クラウド事業部エンジニアの川勝です。

わけあって人感センサで在室確認したいという要望がありまして、やってみることになりました。
ちょっとIoTブームが個人的にきているのもあってAWS IoT Coreをつかってやってみたいと思います。

今回はAWS IoTに送信までやってみます。
使うのサービスとモノは以下。

● AWS IoT
https://aws.amazon.com/jp/iot-core/

AWS IoT Core は、インターネットに接続されたデバイスから、クラウドアプリケーションやその他のデバイスに簡単かつ安全に通信するためのマネージド型クラウドサービスです。

簡単とうたうだけあってチュートリアルのnode.jsとかpythonのデバイス用sdkを使ったものでmacでも簡単に接続まで試すことができました。

● Raspberry Pi
会社に眠っていたもの。多分Raspberry Pi Type B 

● 赤外線モーションセンサ
ググってでてきたRsbeeの3個セットのもの。安い。
Raspberry Piと接続するのにジャンパーワイヤーもいります。

●言語
チュートリアルはnode.jsでやったのですがgoで実装していきます。

Rasberry Pi と モーションセンサ

Raspberry Piのセットアップ

microSD

Raspberry Piを触るのは初めてだったのですが、microSDにosをインストールして使用します。眠っていた機器にもともと8GBのmicroSDは刺さっていたのですが、ちょっと怪しそうだったので家にあった東芝の16GBのものを使いました。
最低8GBは必要みたいです。

OS

CLIでしか操作しなにのでこちらからRaspbian Buster LiteをDLして使用しました。Ubuntu CoreっていうのもIoT向けで良さそうだったのですが今回使うRaspberry Piだと型が古くて対応外みたいでした…残念。

インストール

公式サイトにインストール方法あります。
今回はmac上でフォーマット、OSインストールした後にSSHできるようにします。(Raspberry Piにつなぐようのディスプレイが手元になかったので…)

microSDを接続したら以下コマンドで確認。

$ diskutil list

/dev/disk0 とか /dev/disk1 とかでてくるので要確認。間違うとmac本体がやられます。自分の環境では /dev/disk2 でした。

/dev/disk2 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: FDisk_partition_scheme *7.9 GB disk2
1: Windows_FAT_32 boot 58.7 MB disk2s1
2: Linux 7.9 GB disk2s2

確認できたらフォーマット。

$ diskutil eraseDisk MS-DOS RPI disk2

フォーマットが完了したら一旦アンマウントして…

$ diskutil unmountDisk /dev/disk2DLしたOSのイメージを書き込みます。sudo dd bs=1m if=path_of_your_image.img of=/dev/rdisk2 conv=sync

終わったら、sshできるようにもうひと手間かけます。
もしbootディレクトリがでてこなかったら一度SDカードを取り出して入れるとでてきました。

$ touch /Volumes/boot/ssh

ちなみに Raspberry PiがWifiモデルだったらwifiの設定ファイルを用意しておくと有線で繋がなくても接続できるようです。
wpa_supplicant.confというファイルで以下のような内容をboot以下に保存します。

country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
    ssid="hoge"
    psk="fuga"
}

mac上でここまでできたらSDカードを取り出してRaspberry Piにセットします。

Raspberry Pi へSSH

Raspberry PiはmicroUSB端子の電源アダプタでつなぐと起動します。
が、電源アダプタがなかったのでAmazon Echoの電源で起動しました。
とりあえず動きはするみたい。
ちなみにRaspberry Pi 4では5V 3Aが推奨のようです。
モニターもないのでLANケーブルもつないで電源ライトの赤と緑のランプが付けばOKとしてmacからsshします。
portがわからないので調べます。

$ arp -a
? (192.168.3.1) at 30:::::** on en0 ifscope [ethernet]
? (192.168.3.3) at ac:::::** on en0 ifscope [ethernet]
? (192.168.3.6) at fc:::::** on en0 ifscope [ethernet]
? (192.168.3.8) at 14:::::** on en0 ifscope [ethernet]
? (224.0.0.251) at 1:::::** on en0 ifscope permanent [ethernet]
? (239.255.255.250) at 1:::::** on en0 ifscope permanent [ethernet]

上みたいにIPアドレスとMACアドレスがでてきます。b8:27:e1 から始まるMACアドレスがRaspberry Piらしいのででてくるまで arp -a を連打しましたw
IPアドレスがわかったらSSHします。

$ ssh pi@192.168.xx.xx

初期ユーザはpiでパスワードはraspberryでSSHできます。
パスワードはログイン後に変更しておきましょう。
その他いろいろ設定したほうがよさそうですが、ひとまず接続できたのでAWSの設定と実行プログラムを設置していきます。

AWS IoT

AWSコンソールから作成していきます。

AWS IoTの 管理 > モノ からデバイスの作成をします。

単一のAWS IoT モノの登録を選択します。

Things Registryにデバイスを追加
とりあえず名前だけつけて次へ

証明書を作成します。

ボタンをクリックするだけで新規に証明書が作成されました。
忘れずに証明書、プライベートキー、パブリックキーをダウンロードしましょう。またAWS IoT のルートCAも必要ですのでリンク先からダウンロードします。
ダウンロードしたら証明書を有効化します。
ポリシーをアタッチは先にポリシーを作成しておく必要がありますのでここでは一旦完了します。

モノの一覧にもどるとTestDeviceが作成されていますね!

続いて 安全性 > ポリシー からポリシーの作成をします。

アクションは iot:* としておきます。リソースARNは画像はデフォルトで挿入されている値ですが、接続確認だけなら * にしておいてもよいです。

ポリシーを作成したら 安全性 > 証明書 から証明書にポリシーをアタッチします。

最後に モノ詳細画面からHTTPSエンドポイントを確認しておきます。

センサ接続

ちょっと見にくいですが以下のPINに接続しています。
接続は電源を入れる前に。
1ピン  :5V電源(Power)
6ピン  :0Vアース(GND)
12ピン:GPIO18  (Output)

実行プログラム

ソースはこちら。
goのライブラリでgobotというのを使いました。使いやすくてよかったです。

● Raspberry Pi 用のdriver
https://gobot.io/documentation/platforms/raspi/
● モーションセンサ用
https://gobot.io/documentation/drivers/pir-motion-sensor/

大体そのまま使っています。
モーションセンサで使っているドライバーはRaspberry Pi用のものじゃなくて最初ちょっとハマりました…
あとモーションセンサをつないだpin番号が最初謎でした…
物理ピン番号とGPIOのBCMピン番号があるのですが、物理ピン番号を設定するようです。
Raspberry Piの設定は以下の様になりました。

func newRobot(c MQTT.Client) *gobot.Robot {
// raspiと接続するアダプター
r := raspi.NewAdptor()
// モーションセンサ。pin番号はgpioのpin番号ではない
sensor := gpio.NewPIRMotionDriver(r, pirMotionDriverPin)

// センサの振る舞い
work := func() {
// センサONになったら
sensor.On(gpio.MotionDetected, func(data interface{}) {
fmt.Println(gpio.MotionDetected)
token := c.Publish(fmt.Sprintf(shadowUpdateEndpoint), 0, false, sensorOnMessage)
if token.Wait() && token.Error() != nil {
fmt.Printf("error: %+v", token.Error())
} else {
fmt.Print("Message Publish Success\n")
}
})
// センサOffになったら
sensor.On(gpio.MotionStopped, func(data interface{}) {
fmt.Println(gpio.MotionStopped)
token := c.Publish(shadowUpdateEndpoint, 0, false, sensorOffMessage)
if token.Wait() && token.Error() != nil {
fmt.Printf("error: %+v", token.Error())
} else {
fmt.Print("Message Publish Success\n")
}
})
}

return gobot.NewRobot("motionBot",
[]gobot.Connection{r},
[]gobot.Device{sensor},
work,
)
}

センサのON/OFF時にAWS IoTにMTQQでメッセージを送信します。
あとからきづいたのですがgobotにもMTQQのクライアントがありましたが( https://gobot.io/documentation/platforms/mqtt/ )、違うパッケージを使用しています。
https://github.com/eclipse/paho.mqtt.golang
プログラムでいうと以下あたり

func newTLSConfig() (*tls.Config, error) {
certpool := x509.NewCertPool()
// AWS IoTのルートCA
pemCerts, err := ioutil.ReadFile(caCertificate)
if err == nil {
certpool.AppendCertsFromPEM(pemCerts)
}
// ダウンロードした証明書とプライベートキーのチェック
cert, err := tls.LoadX509KeyPair(clientCertificate, privateKey)
if err != nil {
log.Printf("error: cert file mismatch. clientCertificate: %s, privateKey: %s\n", clientCertificate, privateKey)
return nil, err
}

cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
log.Print("error: parse certificate.\n")
return nil, err
}

return &tls.Config{
RootCAs: certpool,
ClientAuth: tls.NoClientCert,
ClientCAs: nil,
InsecureSkipVerify: true,
Certificates: []tls.Certificate{cert},
}, nil
}

var f MQTT.MessageHandler = func(client MQTT.Client, msg MQTT.Message) {
fmt.Printf("TOPIC: %s\n", msg.Topic())
fmt.Printf("MSG: %s\n", msg.Payload())
}

func mqttClient() (MQTT.Client, error) {
// 証明書の読み込み
tlsconfig, err := newTLSConfig()
if err != nil {
log.Print("error: newTLSConfig error.\n")
return nil, err
}
// AddBrokerでAWS IoTのエンドポイント設定
opts := MQTT.NewClientOptions().
AddBroker(fmt.Sprintf(awsIotHostName, hostName)).
SetClientID(clientID).
SetTLSConfig(tlsconfig).
SetDefaultPublishHandler(f)

return MQTT.NewClient(opts), nil
}

プログラムのbuildはmac上でしてscpでRaspberry Piに転送して動かしました。
実行時のコマンド

./build/deploy/cmd/${PROJECT_NAME} \
-host-name xxxxxxxx.iot.ap-northeast-1.amazonaws.com \
-client-id testDevice-1 \
-client-certificate certificate.pem.crt \
-ca-certificate root-CA.crt \
-private-key private.pem.key \
-thing-name testDevice

引数で証明書のパスとかを指定します。センサのPIN番号も引数にしてもいいかもですね。

このあたりのコマンドはMakefile用意しておいて実行できるようにしています。(引数わすれるので。)
https://github.com/kawakattsun/iot-motion-sensor-go/blob/master/Makefile_go

動かしてみた

(圧縮したので見づらい…)

左がRaspberry Piにsshしてターミナルからコマンド実行した画面。
右はAWS IoTの 管理 > モノ からシャドウという項目をみています。
ちょっと見にくいですが連動して更新されていますね!

ターミナルの表示は
●センサが検知
motion-detected
Message Publish Success

●検知後
motion-stopped
Message Publish Success

どうも検知したあと一定時間で必ずstoppedになるようですね…

この後

次はシャドウの更新をさらにAWS上のサービスに流せるので最終的にLambdaで状態判定して在室検知判定しようと思います。
当初AWS IoTからLambda直で流す予定でしたが、On Offが必ずセットでくるのでkinesis にデータを流して一定期間のレコードからLambdaで判定にしようかな、と思案中。

続きは次回ブログで公開したいと思います。
乞うご期待!