アルプスIoT Smart Moduleの照度データで人の通過を検知して、twilioからSMSで通知

[メカトラックス株式会社様](http://mechatrax.com/)からラズパイIoTスタータキット「[anyPi（エニーパイ）](http://mechatrax.com/products/anypi/)」提供していただいたので、[アルプスIoT Smart Module](http://www.alps.com/j/iotsmart-network/index.html)を接続してみました。アルプス「IoT Smart Module」]の照度センサにより、人が通過するとそのセンサ値の変化をanyPiが感知し、anyPi はPiConsole I/Fの電子ブザーを鳴らします。同時に、anyPiを構成する3GPIを使用してネットワークにPPP接続を行い、Twilio を用いて携帯電話にSMSメッセージを送信します。電子ブザーは、PiConsole I/F上のタクトスイッチが押されると止めます。

<div align="center"><img src="https://qiita-image-store.s3.amazonaws.com/0/124811/dc4c5cf3-c985-9f26-971d-1884ef80929d.jpeg"></div>

anyPiは、下から順番にRaspberry Pi 3、3GPI、PiConsole I/Fと積み重ねられており、今回は、照度センサの照度データがあらかじめ決められた範囲に入れば電子ブザーを鳴らし、タクトスイッチ（白）を押すと電子ブザーを停止します。アルプスIoT Smart Moduleは、Raspberry Pi 3とBluetooth Low Energy（BLE）プロトコルで接続して、照度センサのセンサー情報を取得します。「人が通過する」ことは、手で覆い隠すことにより模擬します。あらかじめ決められた範囲にセンサー値が入れば、電子ブザーと共に、anyPiを構成する3GPIを使用してネットワークにPPP接続を行い、Twilio を用いて携帯電話にSMSメッセージを送信します（実際には、あらかじめ決められた最小送信時間間隔で制限されます）。


#_プログラムの作成_
プログラムはPython言語を使用し、全体を制御する「lightmain.py」、アルプスIoT Smart Moduleを制御してセンサー情報を取得する「alpslight.py」で構成します。また、照度センサから取得したした照度データがあらかじめ決められた範囲に入ったかを調べるために使用する閾値と、Twilio を用いて携帯電話にSMSメッセージを送信する時間間隔の定義を「constant.conf」に設定します。

##_Pythonインタフェース「bluepy」のインストール_
Python言語でBLEを取り扱うためには、次のコマンドでPythonインタフェース「bluepy」をインストールする必要があります。

```
$ sudo apt-get install python-pip libglib2.0-dev
$ sudo pip install bluepy
$ sudo apt-get install git build-essential libglib2.0-dev
$ git clone https://github.com/IanHarvey/bluepy.git
$ cd bluepy
$ sudo python setup.py build
$ sudo python setup.py install
```

##_全体の制御モジュール「lightmain.py」_
「lightmain.py」が最初に起動し、次の処理を行います。

- BLE通信は、「Peripheral」クラスを継承した「AlpsSensor」を使用します
- sendSms関数により、anyPiを構成する3GPIを使用してネットワークにPPP接続し、Twilio を用いて携帯電話にSMSメッセージ「someone there!」を送信します。なおTwilioのアカウントの取得やTwilio電話番号の取得については、「[Raspberry Pi 3でtwilioを使ってSMSメッセージの送信](http://tomosoft.jp/design/?p=9388)」を参照してください。
- buzzer関数により、計算した照度データの範囲チェックを行い、PiConsole I/Fの電子ブザーのON/OFFやsendSms関数の呼び出しを行います。buzzer関数は、NtfyDelegateクラスのhandleNotificationメソッドから、コールバック関数として呼び出されます。
- プログラム起動時に、閾値定義ファイル「constant.conf」から範囲チェックの閾値データやSMSメッセージ送信の最小時間間隔を取得します。

```Python
# -*- coding: utf-8 -*- 
import sys
import time
import signal
sys.path.append('/home/pi/bluepy/bluepy')
from btle import Peripheral
import btle 
import ConfigParser

from twilio.rest import Client
from alpslight import AlpsSensor
import pigpio

Buzzer = 25        #ブザー
SW = 19		#/白

pi = pigpio.pi()
pi.set_mode(Buzzer, pigpio.OUTPUT)

pi.set_mode(SW, pigpio.INPUT)
pi.set_pull_up_down(SW, pigpio.PUD_UP)

def cb_interrupt(gpio, level, tick):
    pi.write(Buzzer, 0)
 
cb = pi.callback(SW, pigpio.FALLING_EDGE, cb_interrupt)

global thresholdLight
global interval
exitflg = False
start = time.time()  # 単位秒
smsflg = False

def exithandler(signal, frame):
    print('exithandler !!')
    exitflg = True

def sendSms():
    global start
    
    start = time.time()

    # Find these values at https://twilio.com/user/account
    account_sid = "ACXXXXXXXXXXXXXXXXX"
    auth_token = "YYYYYYYYYYYYYYYYYY"
    client = Client(account_sid, auth_token)
    
    print("send SMS message \n")

    message = client.api.account.messages.create(to="+12316851234",
                                             from_="+15555555555",
                                             body="someone there!")
    
def buzzer(AmbientLight):
    global smsflg

    print ('AmbientLight:{0:.3f} thresholdLight:{1:.3f}'.format(AmbientLight,thresholdLight))
    if thresholdLight > AmbientLight:
        smsflg = True
        pi.write(Buzzer, 1)
    else :
        pi.write(Buzzer, 0)
    if (start + interval) < time.time() and smsflg :
        smsflg = False
        sendSms()
        
def main():
    global thresholdLight
    global interval

    signal.signal(signal.SIGINT, exithandler)

# 設定ファイル読み込み
    conf = ConfigParser.SafeConfigParser()
    conf.read('constant.conf')
# 範囲チェックデータの保存
    thresholdLight = int(conf.get('THRESHOLD', 'thresholdLight'))
    interval = int(conf.get('TIME', 'interval'))
    print ('interval:{0:.3f}'.format(interval))
    
    alps = AlpsSensor(buzzer)
    alps.setSensor()
    
# Main loop --------
    try:
        while True:
            if exitflg :
                break
            if alps.waitForNotifications(1.0):
            # handleNotification() was called
                continue

            #print ("Waiting...")
            # Perhaps do something else here
    except Exception, e:
        print e, 'error occurred'
    sys.exit(0)

   
if __name__ == "__main__":
    main()

```

##_アルプスIoT Smart Moduleセンサー情報取得「alpslight.py」_

アルプスIoT Smart Moduleの照度センサから照度データを取得するために、アルプスIoT Smart Moduleのコマンドガイド「[Sensor Network Module評価キットApplication Note Command Guide](http://www.alps.com/j/iotsmart-network/pdf/msm_command_manual.pdf)」の「2.2 コマンドによる設定例」を参考にして、次のような設定にしました。

|　　設定　|　 機能|
|:--|:--|
|計測モード|Slowモード|
|計測間隔|1分|
|Acceleration|－|
|Geo-Magnetic|－|
|Pressure|－|
|Humidity|－|
|Temperature|－|
|UV|－|
|Ambient Light|〇|

BLE通信は「writeCharacteristic」メソッドを使用し、アルプスIoT Smart Moduleのコマンドガイド「<a href="http://www.alps.com/j/iotsmart-network/pdf/msm_command_manual.pdf">Sensor Network Module評価キットApplication Note Command Guide</a>」に従って、AlpsSensorクラスのsetSensorメソッドで設定します。Notificationメッセージは、setDelegateメソッドにより設定された NtfyDelegateクラスのhandleNotificationメソッドにより受信します。handleNotificationメソッドでは、モーションデータを含むEvent Codeがデータパケット2「0xF3」のみを処理します。
データパケット内の照度データは符号なし2byte整数で、照度データ[Lx]は「Ambient / (0.05×0.928) ※太陽光及びハロゲンランプ光源時」で求めます。

```python
# -*- coding: utf-8 -*- 
import sys 
sys.path.append('/home/pi/bluepy/bluepy')
from btle import Peripheral
import struct 
import btle
import binascii 

class NtfyDelegate(btle.DefaultDelegate):
    def __init__(self, param, callback):
        btle.DefaultDelegate.__init__(self)
        # ... initialise here
        self.callbackFunc = callback
        
    def handleNotification(self, cHandle, data): 
        # ... perhaps check cHandle
        # ... process 'data'
        cal = binascii.b2a_hex(data) 
        #print u'handleNotification : {0}-{1}:'.format(cHandle, cal)
        
        #if int((cal[0:2]), 16) == 0xf2:
        #    print 'cal:{0}'.format(type(cal)) 
        if int((cal[0:2]), 16) == 0xf3:
            #print 'cal:{0}'.format(type(cal)) 
            AmbientLight = int((cal[22:24] + cal[20:22]), 16) / (0.05*0.928)
            #AmbientLight = int((cal[22:24] + cal[20:22]), 16) 
            
            print (' Ambient Light {0:.3f}[Lx] '.format(AmbientLight))
            self.callbackFunc(AmbientLight)
            
class AlpsSensor(Peripheral):
    def __init__(self,callback):
        Peripheral.__init__(self,"28:A1:83:E1:58:96")
        callbackFunc = callback
        self.setDelegate( NtfyDelegate(btle.DefaultDelegate,callback))
        
    def setSensor(self):
        # Ambient Light検知 5s間隔
        #alps.writeCharacteristic(0x0018, struct.pack('<bbb', 0x20, 0x03, 0x00), True)
        #alps.writeCharacteristic(0x0018, struct.pack('<bbb', 0x20, 0x03, 0x01), True)
        
        self.writeCharacteristic(0x0013, struct.pack('<bb', 0x01, 0x00), True)
        self.writeCharacteristic(0x0016, struct.pack('<bb', 0x01, 0x00), True)
        
        self.writeCharacteristic(0x0018, struct.pack('<bbb', 0x2F, 0x03, 0x03), True)
        self.writeCharacteristic(0x0018, struct.pack('<bbb', 0x01, 0x03, 0x40), True) # only Ambient Light
        self.writeCharacteristic(0x0018, struct.pack('<bbb', 0x04, 0x03, 0x00), True) # Slowモード
        self.writeCharacteristic(0x0018, struct.pack('<bbbb', 0x05, 0x04, 0x05, 0x00), True) # 5sec
        
        self.writeCharacteristic(0x0018, struct.pack('<bbb', 0x2F, 0x03, 0x01), True)
        self.writeCharacteristic(0x0018, struct.pack('<bbb', 0x20, 0x03, 0x01), True)
            



```

##_閾値定義ファイル「constant.conf」_
計算した照度データがあらかじめ決められた範囲に入ったかを調べるために使用する閾値「thresholdLight 」は次のように設定されます。時間間隔「interval」は、前回Twilio を用いてSMSメッセージを送信してからの最小時間間隔となり、この時間間隔以下ではSMSメッセージは送信されません。単位は「秒」となります。この値はプログラム起動時に読み込まれます。

```
# 閾値データ
[THRESHOLD]
thresholdLight = 50
[TIME]
interval = 60

```

#_プログラムの実行_
プログラムを実行し、アルプスIoT Smart Moduleを手で覆うと電子ブザーが鳴ります。この場合１分以内（閾値定義ファイル「constant.conf」の定義に依存する）に携帯電話にSMSメッセージ「someone there!」が送信されます。携帯電話で受信されたSMSメッセージを次に示します。

<div align="center"><img src="https://qiita-image-store.s3.amazonaws.com/0/124811/9e2394aa-b362-50cc-93c4-ba7305bf667e.jpeg"></div>

また、実行画面に表示される照度センサからの計算結果を次に示します。「Ambient Light」が照度センサから入力して変換した結果です。

```
$ python lightmain.py
interval:60.000
 Ambient Light 431.034[Lx]
AmbientLight:431.034 thresholdLight:50.000
 Ambient Light 474.138[Lx]
AmbientLight:474.138 thresholdLight:50.000
 Ambient Light 474.138[Lx]
AmbientLight:474.138 thresholdLight:50.000
 Ambient Light 387.931[Lx]
AmbientLight:387.931 thresholdLight:50.000
 Ambient Light 21.552[Lx]
AmbientLight:21.552 thresholdLight:50.000
 Ambient Light 0.000[Lx]
AmbientLight:0.000 thresholdLight:50.000
 Ambient Light 0.000[Lx]
AmbientLight:0.000 thresholdLight:50.000
 Ambient Light 150.862[Lx]
AmbientLight:150.862 thresholdLight:50.000
 Ambient Light 258.621[Lx]
AmbientLight:258.621 thresholdLight:50.000
 Ambient Light 237.069[Lx]
AmbientLight:237.069 thresholdLight:50.000
 Ambient Light 150.862[Lx]
AmbientLight:150.862 thresholdLight:50.000
^Cexithandler !!
Helper exited error occurred
Exception IOError: (32, 'Broken pipe') in <bound method AlpsSensor.__del__ of <alpslight.AlpsSensor instance at 0x76419d00>> ignored


```