IoTを使って畑を監視する

sparkgene
1696

この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2015 の投稿記事です。

畑を監視する?

こんにちはsparkgeneです。

最近は趣味で畑を借りて作物を育てている人が増えてきているようです。しかし趣味で畑仕事をしていると、面倒を見れるのが週末だけだったりしてちょっと予定があって畑に行けない事も出てきます。そして水が足りなくてせっかく育ててきた作物をダメにしてしまうことも多々あるようです。

こんな悩みをIotを使って畑の状態を監視して、少しでも被害を減らせるようにする仕組みを考えてみました。

この仕組みで使うモノ・サービス

スクリーンショット 2015-12-08 10.41.48

監視に使うセンサー類

今回は以下の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はデジタルの温度&湿度センサーです。

IMG_6674

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はデジタルの光センサーです。

IMG_6720
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 apt-get install i2c-tools

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ライブラリを使うことで簡単に扱うことが出来ます。

sudo apt-get install 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種類の方法で取得することが出来ます。

IMG_6718

今回はアナログ値で取得してみたいのですが、Raspberry Piはアナログの入出力に対応していません。そこで、A/Dコンバータ(ADC)を使ってアナログ値をRaspberry Piでも取得できるようにします。

MCP3008(A/Dコンバータ)

IMG_6717
MCP3008は10bitのADCですので、アナログ値を0〜1023までのデジタルな値に変換してくれます。今回は電源として3.3vを利用しておりVREFに3.3vを印加しているので、1023の時は電極同士が未通電(=カラカラに乾燥)と判断でき、0の時は電極同士が短絡状態(=水分が増えることで抵抗がなくなる状態)と判断することが出来ます

MCP3008はアナログの入力をデジタル値として取得できるようになるのですが、Raspberry PiとはSPI(Serial Peripheral Interface)を使ってデジタル変換されたアナログ値を取得します。MCP3008とRaspberry Piの配線とソースはadafruitの記事を参考にしました。

配線が終わったら実際に値を取得するために必要なセットアップを行います。

sudo easy_install rpi.gpio

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を選択します。

スクリーンショット 2015-12-07 11.04.27

名称を付けてCreateボタンをクリックすると、リソースの一覧に作成したデバイスが表示されます。

証明書を作成

AWS Iotでは証明書をデバイスにインストールしておくことで、デバイスの認証を行います。

スクリーンショット 2015-11-30 22.08.091-clickの方で作成します。

スクリーンショット 2015-11-30 22.08.25作成するとダウンロードリンクが表示されるので、ダウンロードします。

スクリーンショット 2015-11-30 22.10.46

デバイスと同じく作成すると、リソースのところに証明書が表示されます。今の状態だとinactiveになっているので、証明書を選択して右上のメニューからActivateを選んで、有効にします。

スクリーンショット 2015-11-30 22.10.58

ステータスがActiveになりました。

スクリーンショット 2015-11-30 22.11.08

ポリシーを作成

デバイスに対して、AWS IoTの各種操作を許可するためのポリシーを作成します。

スクリーンショット 2015-11-30 22.12.30

Action ポリシーで許可する操作はiot関連全て
Resource 許可対象のリソースは全て

各種リソースを紐付ける

スクリーンショット 2015-11-30 22.12.51

証明書とポリシーのひも付けをするので、証明書を選択し、右上のメニューからAttach a Policyを選びます。

スクリーンショット 2015-11-30 22.13.18

確認のダイアログが表示されるので、先ほど作成したポリシーの名前を入力します。

スクリーンショット 2015-11-30 22.13.43

次にデバイスと証明書のひも付けを行います。先ほどと同じように証明書を選択した状態にして右上のメニューからAttach a thingを選びます。

スクリーンショット 2015-11-30 22.14.05

確認のダイアログで最初に追加したデバイスの名前を入力します。

スクリーンショット 2015-11-30 22.14.30

ルールを作成

画面上部のCreate a resourceをクリックし、リソースの中からCreate a Ruleをクリックします。

スクリーンショット 2015-12-06 16.08.27

RuleではIoTデバイスから送られてくるデータをSQLライクな言語で抽出することが出来ます。これにより、色んな種類のデバイスから色んなデータが送られてきても、必要なデータのみを取り出しすことが出来ます。

Attribute 取り出すデータ
Topic Filter デバイスで指定されたトピックを指定

スクリーンショット 2015-12-06 16.09.29
Choose an actioninsert 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でアクションを追加します。

スクリーンショット 2015-12-05 21.10.54
最後にCreateをクリックしてRuleを作成します。

データを送信する

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.pyiot_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側を見てみます。

スクリーンショット 2015-12-06 16.11.09AWS Iotにデータが送られるとCloudWatch Logsにログが吐かれます。もしデータが登録されない場合は、このログを見て原因を調査します。

スクリーンショット 2015-12-06 16.11.29DynamoDBを確認すると、正しくデータが登録されていることが確認できました。

ネットワーク環境を整える

会社や家ではWi-Fiなどネットワーク環境が整っていますが、当然畑にはWi-Fi環境はありません。その為、Raspberry Piからデータを送るために、ポケットWi-Fiや3Gのモデムを必要とします。今回はSORACOM Airのsimと3G USBドングル FS01BUを使って、Raspberry Piをインターネットに繋ぎます。

IMG_6725

こちらを参考に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を使います。

sudo apt-get install supervisor

センサー取得スクリプトの自動実行を設定します。

[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の電源をいれるだけで、ネットワークへ接続し収集したデータをアップロードします。

実際に監視してみる

一通り動くものが出来上がったので、実際にプランター畑を監視してみましょう。

IMG_6728

先程はDynamoDBにデータが登録されるのを確認しましたが、それだと視認性が悪いのでCloudWatchを使ってビジュアライズします。やり方としてはDynamoDB StreamsのイベントでLambdaが実行できるので、これを使ってDynamoDBに登録されたデータをLambda経由でCloudWatchに登録するようにします。

AWS IoTにLambdaと連携するactionを追加することで同じことが実現できますが、DynamoDB streamを使ってみたかったので上記の方法になっています。データとして蓄積する必要が無い場合は、DynamoDBに入れずにLambda経由でCloudWatchに登録するほうが楽かもしれない。

DynamoDBのStreamを編集

スクリーンショット 2015-12-07 23.55.42
DynamoDBのテーブルを選択するとStreamの設定ができるのでManage Streamをクリックします。

スクリーンショット 2015-12-06 22.15.24
変更後の情報を取り出すNew imageを選択します。

Lambdaファンクションを作成

スクリーンショット 2015-12-08 0.13.44
テンプレートの中からdynamodb-process-stream-pythonを選択します。

スクリーンショット 2015-12-08 0.15.40
DynamoDB tableには作成したテーブル名を指定し、Starting positionでは時系列順に処理するので古いものから順に読み取る「Trim horizon」を指定します。

Lambdaファンクションのソースにはサンプルソースlambda_function.pyを貼り付けて保存します。

しばらく待ってると…

スクリーンショット 2015-12-08 0.34.08
センサーから読み取った数値がグラフとして見るようになりました。CloudWatchのDashboardを作ると、見たいグラフだけを綺麗に並べることが出来ます。

ちなみにこのグラフは朝の6時頃から昼ぐらいまでのデータなので、tempture(気温)とlux(明るさ)の数値が上がっていくに連れて、土の水分量が変わっていくのが確認できると思います。

まとめ

AWSのマネージドサービスを利用することでサーバを立てることなく、センサーから取得したデータをグラフするとこまで出来ました。サーバ立てないで済むということはそれだけ運用コストも削減できるので非常に便利ですね。CloudWatchを使っているのでセンサーから取得した値に対してアラームも設定することが出来、乾燥していると判断したらSNS経由でメールを送って通知することも可能です。

また、LambdaでCloudWatchに書き込むとともにS3にデータを保存しておくと、Amazon Machine Learningで機械学習を行うことも可能で、乾き過ぎとか水多すぎて根腐れをしてしまうと言った事もデータの傾向で知ることも出来ると思います。

SORACOMのsim+USBドングルもすごく便利でした。Raspberry Piの3Gシールドは結構高いのですが、この組み合わせだと他でも使えそうですし、費用も抑えられます。SORACOMの画面で通信料が見られるのですが、1時間毎のグラフもあったりして分かりやすいです。

スクリーンショット 2015-12-08 15.15.04

見えてきた課題

実際に作り込むことで見えてきた課題としては、以下のものがありました。

電源問題

畑にはWi-Fiも無いですが、そもそも電源も無いことが多いです。今回作ったものを実際の畑で使う為には、電源を用意する必要があります。今考えているのはソーラーパネルとバッテリーを組み合わせたシステムの導入です(1万円ぐらいで揃いそう)。また、今回はスクリプトを実行しっぱなしにし、3G回線も繋ぎっぱなしのスクリプトを書きましたが、消費電力や通信費を考えると、1時間に1回取得したデータを一気にアップロードするといった、やり方も必要かと思います。
IMG_6744
ちなみに、全部動作している状態の消費電力は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