Arduino
xbee
RaspberryPi

家庭菜園の土の乾燥状態等をリモート監視する(将来的には自動水まき機能付加の予定)

More than 1 year has passed since last update.

目的

庭で家庭菜園をやってみようと一念発起してみたはいいものの、ちょっと旅行したり、目を離した隙に水やりを忘れて枯れていたら哀しいので、各種センサーを活用して、ある程度自動的に菜園を操業出来ないだろうかと思い、作ってみました。

全部一度にやろうとすると中々進まないので、下記のような感じでバージョンアップしていこうと思います。

  1. 菜園の状態(光量、温度、湿度、土の湿り気)を一定間隔でモニタリングし、インターネット経由で外部からも菜園の状態を監視できるようにする
    (注)今回の内容はここまで
  2. 土の湿り気等の情報を元に、菜園に自動的に散水する(他にも何かやることないかな…)
  3. 電源となる太陽光パネルを赤道儀のように動かす(できたらいいな…)

全体の構成

お粗末な絵ですが、全体像はこんな感じです。

description.png

もう少し詳しく書くと、下記のような構成になります。

  • 菜園
    • 太陽光パネル 20W+バッテリ(わたしたち電力さん主催のミニミニ太陽光発電ワークショップにて製作)
    • Arduino Pro Mini (3.3V 8MHz)
  • 自宅
    • Raspberry Pi
      • 無線通信モジュール(XBee ZB with Coordinatorモード)
      • Arduinoが定期的に送ってくるセンサー値をXivelyに送信

作り方

下記の順に設定/制作していきます。菜園のXBeeは、低消費電力重視でEnd Deviceモードにしたかったのですが、設定に手間取ってうまくいかず、Routerとして使用します。End Deviceは次回に持ち越し…。

  • 2つのXBee ZBをそれぞれCoordinator/Routerに設定する
  • Arduino側の状態監視ノード作成
  • Raspberry Pi側のサーバ環境構築

2つのXBee ZBをそれぞれCoordinator/Routerに設定する

XBee ZBのfirmwareを書き換えたり(Coordinator/Router/End Deviceを切り替える)、AT/APIモードを変更したり、PAN ID等を設定したりするには、以下のツールと機材が必要です。

XBee ZBを買った時のまま(ATモード)で使用すると、1対1での通信しか出来ない上、サーバ側でシリアルメッセージを自前でパースする必要がある等、色々と不便なので、APIモードで使用します。

Routerの設定

  1. XBee USBインターフェースボードにXBee ZBを載せて、X-CTUをインストールしたPCのUSBに接続する。
  2. X-CTUを起動し、XBee ZBを検索する。
    予めXBee ZBに設定されているBaurateを知っているのであれば、図の左側の「+」ボタンを押して探す方が早い。何が設定されているか忘れてしまった/知らない場合等は、右側の「虫眼鏡」ボタンを押して、探すパラメータを一通り選択して探すと良い。
    xbee_search.png
  3. 見つかったXBee ZBを選択して検索用ダイアログを抜けると、下の図のような状態になる。
    Update Firmwareボタン(ICチップに下矢印のアイコン)を押し、Function setからZigbee Router APIを選択し、Firmware VersionからNewestを選択して、Finishボタンを押すと、APIモードのRouter用Firmwareがインストールされる。 スクリーンショット 2014-09-16 11.29.12.png
  4. Firmwareインストール完了後、下記のような値を設定する。
    PAN IDとScan Cahnnelは、有効範囲内で好きな値を設定して良い。ただし、通信相手となるCordinatorに設定する値と合わせる必要がある。また、DH/DL(Destination address High/Low)は、デフォルトの0のままでCoordinatorを意味するので、そのまま変更しない。

    ID(PAN ID) : 1234
    SC(Scan Channel) : 1
    AP(API Enable) : 2
    
  5. XBeeをEnd Deviceに設定すると、デフォルトでSleep ModeがCyclic Sleep[4]に設定される上、Time Before Sleepが1388 msに設定される。このため、最後のシリアル通信があってから1388 ms後には、EndDeviceは勝手にスリープしてしまう。
    スリープする/しないはArduino側からコントロールしたいので、この設定を下記のように変更する。このモードでスリープさせておけば、10μA未満の電流しか消費しないらしい。スリープからの復帰には13 ms必要。
    今回はRouterで動作させるので、下記の設定は適用せず、デフォルト設定のままで動かす。

    SM(Sleep Mode) : Pin Hibernate[1]
    D7(CTS) : Disable
    

Coordinatorの設定

  1. Routerの設定をした直後の状態であれば、一旦Radio Modules一覧の×ボタンを押して接続を解除する。
  2. XBee USBインターフェースボードにもう一方のXBee ZBを載せて、PCのUSBに接続する。
  3. X-CTUを起動し、XBee ZBを検索する。
  4. Update Firmwareボタンを押し、Function setからZigbee Coordinator APIを選択し、Firmware VersionからNewestを選択して、Finishボタンを押すと、APIモードのCoordinator用Firmwareがインストールされる。
  5. Firmwareインストール完了後、ID(PAN ID)、SC(Scan Channel)、AP(API Enable) にRouterに設定したものと同じ値を設定する。また、DH/DL(Destination address High/Low)は、デフォルトの0のままで問題ない。今回の構成では、Coordinatorはデータを受け取るだけなので、Destination(送信先)は必要ない。

Arduino側の状態監視ノード作成

菜園の状態を定期的に監視してくれるボード類を製作します。Friztingを使い、簡単な図を書いてから取りかかりました。

  1. 今後のことを考えて、Arduino本体を実装するボードと、センサー類を実装するボードを分離する。
    1. Arduino本体を実装するボード
      乾電池は、実際は太陽光発電システム
      End Device Routerに設定したXBeeを接続
      4Pコネクタは将来的なi2cデバイス拡張用
      6Pコネクタはセンサー類実装ボードとの接続用
      XBeeは3.3V駆動だが、12V電源に直結(秋月電子のピッチ変換基板がレギュレータを内蔵しているため) node_center_ブレッドボード.png
    2. センサー類を実装するボード
      4Pコネクタは土の湿り気センサー接続用
      6PコネクタはArduino本体実装ボードとの接続用
      node_garden_ブレッドボード.png
  2. 上記の2つのボードは、図にある6Pコネクタ同士をケーブルで繋ぐ。
    完成写真は下記の通り。
    写真 1.JPG
  3. 各センサーから読み出した値をXBee経由で送出するためのコードを書く。主要な処理は以下の通り。コメントやデバッグ用のコードは省いているので、詳細はGithubにpush済みのコードを参照ください。また、ここでloopの間隔を5秒未満に設定しないよう、注意する。詳細はRapberry Piサーバ環境設定を参照。

    home-automation/nodes/gardening/gardening.ino
    /****************************
     * main routine
     ****************************/
    void setup()
    {
        Serial.begin(XBEE_SERIAL_BAURATE);
        myXBee.setSerial(Serial);
    
        myHumidity.begin();
        myLight.begin();
    
        myLight.setTiming(LIGHT_GAIN, LIGHT_INTEGRATION_TIME_INDEX , lightIntegrationTime);
        myLight.setPowerUp();
    
        if(mainLoopInterval < lightIntegrationTime)
            mainLoopInterval = lightIntegrationTime;
    }
    
    void loop()
    {
        unsigned int lightData0 = 0;
        unsigned int lightData1 = 0;
        double nowLuxDouble = 0.0;
        SENSOR_DATA sensorData[E_SENSOR_MAX] =
        {
            {{'L', 'U', 'X'}, '0', 0},
            {{'M', 'O', 'I'}, '0', 0}, // for small planter
            {{'M', 'O', 'I'}, '1', 0}, // for large planter
            {{'H', 'U', 'M'}, '0', 0},
            {{'T', 'M', 'P'}, '0', 0},
        };
    
        if (isTriggerToPost() != true)
            return;
    
        if (myLight.getData(lightData0, lightData1))
        {
            myLight.getLux(LIGHT_GAIN, lightIntegrationTime, lightData0, lightData1, nowLuxDouble);
            sensorData[E_SENSOR_LUMINOSITY].value = (long)(10 * nowLuxDouble);
        }
    
        sensorData[E_SENSOR_TEMPERATURE].value = (long)(10 * myHumidity.readTemperature());
        sensorData[E_SENSOR_HUMIDITY].value    = (long)(10 * myHumidity.readHumidity());
        sensorData[E_SENSOR_MOISTURE0].value   = (long)(10 * myMoisture0.getMoisturePercent());
        sensorData[E_SENSOR_MOISTURE1].value   = (long)(10 * myMoisture1.getMoisturePercent());
    
        ZBTxRequest myTxRequest = ZBTxRequest(
                addrContributor,
                (uint8_t*)sensorData,
                sizeof(sensorData));
    
        myXBee.send(myTxRequest);
    
        // this function takes 500ms as max when timeout error.
        indicateStatusXBee(myXBee);
    }
    
  4. 上記コードをコンパイルし、Arduinoに書き込む。この時、XBeeをArduinoと接続しない。接続したままだとArduinoの書き込みに失敗してしまうので、注意する。

Raspberry Pi側のサーバ環境構築

菜園側から送られるデータを、Raspberry PiはXBeeで受け、Xivelyに送信してグラフ化します。XBee/Xively共にPythonライブラリが公開されているので、サーバ側の処理はPythonで書きます。Raspberry Piに下記のライブラリをインストールしておいてください。

  1. Cordinatorに設定したXBeeをRaspberry PiのUART TX/RXに接続し、VCCに+5Vを接続しておく。XBeeは3.3V駆動だが、秋月電子殿のピッチ変換基板がレギュレータ(入力+4〜+20V)を内蔵しているため。
    server_ブレッドボード.png
  2. XBeeをRaspberry Piのシリアルに接続して使うため、下記のようにシリアル経由のシェルを無効化する。

    /etc/inittab
    #T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
    
    /boot/cmdline.txt
    #dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/sda1 rootfstype=ext4 elevator=deadline rootwait
    dwc_otg.lpm_enable=0 console=tty1 root=/dev/sda1 rootfstype=ext4 elevator=deadline rootwait
    
  3. xbeeモジュールを確認する。今回使用するのはXBee ZBなので、xbee.ZigBeeを使えば良いことが分かる。(これを使用してるexampleのスクリプトが無かったため、ちょっとハマった)

    $ python3
    >>> import xbee
    >>> for item in dir(xbee):
    ...     print(item)
    XBee
    ZigBee
    >>> help(xbee.XBee)
    Help on class XBee in module xbee.ieee:
    
    class XBee(xbee.base.XBeeBase)
     |  Provides an implementation of the XBee API for IEEE 802.15.4 modules
     |  with recent firmware.
    
    >>> help(xbee.ZigBee)
    Help on class ZigBee in module xbee.zigbee:
    
    class ZigBee(xbee.base.XBeeBase)
     |  Provides an implementation of the XBee API for XBee ZB (ZigBee) modules
     |  with recent firmware.
    
  4. Arduino側が正常に動作している状態で、python-xbeeに含まれるexampleスクリプトをxbee.ZigBeeを使うように変更した上で実行してみると、下記のように、送られてくる生データを確認することが出来る。

    $ python2 ./receive_samples_series_2.py
    {'source_addr_long': '\x00\x13\xa2\x00@\xad\xd0\x8b', 'rf_data': '\xa5Z', 'source_addr': 'g\x9c', 'id': 'rx', 'options': 'A'}
    {'source_addr_long': '\x00\x13\xa2\x00@\xad\xd0\x8b', 'rf_data': '\xa5Z', 'source_addr': 'g\x9c', 'id': 'rx', 'options': 'A'}
    {'source_addr_long': '\x00\x13\xa2\x00@\xad\xd0\x8b', 'rf_data': '\xa5Z', 'source_addr': 'g\x9c', 'id': 'rx', 'options': 'A'}
    {'source_addr_long': '\x00\x13\xa2\x00@\xad\xd0\x8b', 'rf_data': '\xa5Z', 'source_addr': 'g\x9c', 'id': 'rx', 'options': 'A'}
    
  5. うまく動かない(データの送受信がうまくいかない)場合は、XBeeそのものが正常に動作するか確認するため、AT(透過)モードでデバッグポート代わりに動作させてみる等、色々やってみる。

  6. 今度は、Arduinoが送ってくるデータのフォーマットに合わせて、PythonでパースしてからXivelyに送信するよう、実装する。Xivelyのドキュメントにあるとおり、最小のデータ送信間隔は5秒なので注意する。間違って1秒間隔で送信してしまったりすると、HTTP Errorが返ってきて、少しの間Xivelyとの通信が出来なくなることがあるので注意。

    home-automation/server/gardening.py
    #!/usr/bin/env python3
    
    from xbee import ZigBee
    from requests.exceptions import HTTPError
    import sys
    import datetime
    import struct
    import time
    import serial
    import xively
    
    _XBEE_PORT = '/dev/ttyAMA0'
    _XBEE_BAUDRATE = 9600
    
    _XIVELY_FEED_ID = 1779591762
    _XIVELY_ID_TABLE = \
    {
            'LUX0':'Luminosity',
            'HUM0':'Humidity',
            'MOI0':'MoistureOfSmallPlant',
            'MOI1':'MoistureOfLargePlant',
            'TMP0':'Temperature',
    }
    
    def message_received(feed, data, now):
        source_addr_long_raw = [byte for byte in struct.unpack('BBBBBBBB', data['source_addr_long'])]
    
        source_addr_long_high = 0
        for i in range(0,4):
            source_addr_long_high |= source_addr_long_raw[i] << 8 * (3 - i)
    
        source_addr_long_low = 0
        for i in range(4,8):
            source_addr_long_low |= source_addr_long_raw[i] << 8 * (7 - i)
    
        source_addr_raw = [byte for byte in struct.unpack('BB', data['source_addr'])]
    
        source_addr = 0
        for i in range(0,2):
            source_addr |= source_addr_raw[i] << 8 * (1 - i)
    
        rf_data = struct.unpack('4sl4sl4sl4sl4sl', data['rf_data'])
    
        datastreams = []
        for rf_word_num in range(0,5):
            sensor_type  = rf_data[rf_word_num*2+0].decode('ascii')
            sensor_value = rf_data[rf_word_num*2+1] / 10.0
            datastreams.append(xively.Datastream(id=_XIVELY_ID_TABLE[sensor_type], current_value=sensor_value, at=now))
    
        feed.datastreams = datastreams
        feed.update()
    
    if __name__ == '__main__':
        ser = serial.Serial(_XBEE_PORT, _XBEE_BAUDRATE)
    
        kargs = {}
        kargs['escaped'] = True
        #kargs['callback'] = message_received
    
        if len(sys.argv) > 1:
            if 'debug' in sys.argv:
                xbee = None
            else:
                xbee = ZigBee(ser, **kargs)
                api = xively.XivelyAPIClient(sys.argv[1])
                feed = api.feeds.get(_XIVELY_FEED_ID)
        else:
            raise SystemError('Xively API key is required.')
    
        while True:
            try:
                if xbee is None:
                    ser.read()
                else:
                    data = xbee.wait_read_frame()
                    now = datetime.datetime.utcnow()
                    message_received(feed, data, now)
            except HTTPError:
                print('HTTPError occurs.')
                time.sleep(600)
                continue
            except KeyboardInterrupt:
                break
    
        # halt() must be called before closing the serial
        # port in order to ensure proper thread shutdown
        if xbee is not None:
            xbee.halt()
        ser.close()
    
  7. Raspberry Pi上で、上記のPythonスクリプトを下記のように走らせておけば、定期的にXivelyへセンサー値がアップロードされる。"h22..."の部分は、Xively上で設定したAPI key文字列を指定する。

    $ nohup ./home-automation/server/gardening.py h22... &
    
  8. うまく行けば、リンク先のように、インターネット経由でどこからでも家庭菜園の状態を監視する事が出来る。

    スクリーンショット 2014-09-24 22.07.39.png

まとめ

ArduinoとRaspberry Piを組み合わせて、実用的で且つ面白いことが出来ないかと思い、いろんな勉強も兼ねてやってみましたが、想像していたよりもかなり大変でした。特に得体の知れないXBeeという無線デバイスのAPIモードでの動作確認では、正常に動作しない場合の調査や解決に至るまでの過程が色々あって…。

また、2歳児の子供の妨害をかいくぐって製作、コーディング、テストするのは至難の業でした…とはいえ、聞き分けの良い子なのが幸いして、意外と早く一応の完成にこぎつける事が出来ました。

次は、本来の目的だった自動水まき機能の追加を目指して、頑張ります。

おまけ

昨日から稼働し始めた、我が家の菜園監視システムです。

写真.JPG