概要
下記2つの記事のRaspberry Pi3(以後ラズパイ)版。
ラズパイでボタンを押したら勤怠連絡が飛ぶようにしてみたいと思います。
やること
- ラズパイにスイッチをつける
- ラズパイのスイッチを押すと、SORACOM経由でAWSへ
- SORACOM経由での通信はMQTTで、MQTTSにする部分はSORACOM Beamで吸収
- AWSへの部分はAWS IoTで実現
- AWS IoTからはDynamoDBへデータを保存
- AWS DynamoDBにデータが保存されたら、AWS Lambdaをキック
- AWS LambdaからmyThings Developers経由で勤怠連絡
- 勤怠連絡にはSlack、Y!メールを利用
用意するもの
- Raspberry Pi3
- 3G USB ドングル (FS01BU)
- SORACOM Air
- AWS IoT
- AWS DynamoDB
- AWS Lambda
- myThings Developers
事前準備
- ラズパイの準備とSORACOMの接続は下記2つの記事を参照
- myThings Developersの設定は下記の記事を参照
- AWS DynamoDBは下記記事の「AWS DynamoDBの準備」を参照
やったこと
上記のやることの1〜6までの作業をやっていく。
順番は若干違いますが、一通りやってみました。
AWS IoTの準備
AWS IoTで所々行ったことをメモしておきます。
Thingの作成
- AWS IoTコンソールの「Registry」から「Register thing」をクリック

- Thing名を入力して「Create thing」をクリック

- 下記のようになっていれば作成完了

ポリシーの作成
- Thingに対して、AWS IoTの各種操作を許可するためのポリシーを作成

- ポリシーの名前を設定し、Actionを「iot:」、Resourceを「」、Allowにチェックボックスを入れる。これで、iotのすべての操作に対して許可をする設定になります

証明書の作成
- 上記完了画面の「Security」から「Create certificate」をクリック

- 完了すると下記の画面になるので、3つの証明書(Downloadリンク)をダウンロードし、「Activate」ボタンをクリック

証明書にアタッチ
- 証明書にPolicyをアタッチ


- 証明書にThingをアタッチ


Lambdaの準備①
AWS IoTのRuleに追加するためのLambdaファンクションを用意する。
本来は下記のいずれかでよかったのですが、DynamoDBへの書き込みが意図通りにいかなそうだったので改めて用意。
- myThingsとAmazon Dash ButtonをAWS経由で繋げてみるで用意したLambdaファンクションを利用
- AWS IoTのRuleで直接DynamoDBへ書き込み(MacAddressを追加する方法がわからなかったので一旦これはなし)
用意したLambdaファンクションは下記
'use strict';
//var uuid = require('node-uuid');
const qs = require("querystring");
var date = new Date();
var AWS = require("aws-sdk");
var dynamo = new AWS.DynamoDB.DocumentClient();
exports.handler = (event, context, callback) => {
// パラメータチェック
if (!event.macAddress) {
context.fail('macAddress is not specified')
}
// uuidを生成
//var uuid = uuid.v4();
var uuid = createUuid();
// 更新内容をセット
var item = {
"logId": uuid,
"createdAt": Math.floor(date.getTime() / 1000),
"macAddress": event.macAddress
};
var param = {
TableName: 'Button',
Item: item
}
dynamo.put(param, function(err, data) {
if (err) {
context.fail(err);
} else {
context.succeed(item)
}
});
};
/**
* UUID(ランダム文字列)の生成
* @return string UUID
*/
function createUuid() {
var S4 = function() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
}
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4() +S4());
}
Ruleの作成
- Rulesから「Create a rule」をクリック

- NameとDescriptionを入力

- Message Sourceの設定

- 「Add action」で下記の画面に遷移し、Lambdaを選択

- 先ほど作成したLambdaファンクションを選択

- 下記のようになっていれば「Create rule」をクリック

- これでRuleの完成

動作確認
$ /usr/local/opt/mosquitto/bin/mosquitto_pub --cafile Downloads/rootCA.pem \
> --cert <<証明書>>.crt \
> --key <<プライベートキー>>.key \
> -h <<エンドポイント>>.iot.us-west-2.amazonaws.com \
> -p 8883 -d -t topic/button \
> -m '{"macAddress": "XX:XX:XX:XX:XX:XX"}'
Client mosqpub/16956-YukinoMac sending CONNECT
Client mosqpub/16956-YukinoMac received CONNACK
Client mosqpub/16956-YukinoMac sending PUBLISH (d0, q0, r0, m1, 'topic/button', ... (35 bytes))
Client mosqpub/16956-YukinoMac sending DISCONNECT
- DynamoDBに保存されていれば成功!!
SORACOM Beamの準備
SORACOM Beamの設定はSORACOMのユーザーコンソールのグループ設定から行います。
グループ設定
- メニューのグループからグループを追加

- MQTTエントリポイントを設定。下記のように設定したら、証明書をONにする。


- 証明書の設定。上記でダウンロードした3つの証明書を下記のように入力。

- 上記で設定したグループに、ラズパイに接続しているSIMを追加

ラズパイとの接続確認
- MQTTのクライアントのMosquittoをインストール
$ sudo apt-get install -y mosquitto-clients
パッケージリストを読み込んでいます... 完了
・・・・・
mosquitto-clients (1.3.4-2) を設定しています ...
libc-bin (2.19-18+deb8u6) のトリガを処理しています ...
- インストールしたMosquittoクライアントを使って、AWS IoTにリクエストを投げてみる
$ mosquitto_pub -d -h beam.soracom.io -t topic/button -m '{"macAddress": "XX:XX:XX:XX:XX:XX"}' -p 1883
Client mosqpub/4091-darmaso-ra sending CONNECT
Client mosqpub/4091-darmaso-ra received CONNACK
Client mosqpub/4091-darmaso-ra sending PUBLISH (d0, q0, r0, m1, 'topic/button', ... (35 bytes))
Client mosqpub/4091-darmaso-ra sending DISCONNECT
- DynamoDBに保存されていれば成功!!
AWS Lambdaの準備②
DynamoDBに保存されたら、myThings DevelopersのAPIへリクエストする部分を作成する。
- Lambdaのファンクション生成画面で「dynamodb-process-stream」を選択

- DynamoDB tableは準備済みのテーブル、Starting PositionはLateast、Enable Triggerにチェックを入れて次へ

- コードはNode.js 4.3を選択。ソールコードは下記。
'use strict';
const qs = require("querystring");
var https = require("https");
var date = new Date();
var dateString = createPostTimeString();
// myThings Developersに必要なリクエスト項目
var appid = "<<自分のAppID>>";
var secret = "<<自分のsecret>>";
var accessToken = "<<自分のアクセストークン>>";
var refreshToken = "<<自分のリフレッシュトークン>>";
exports.handler = (event, context, callback) => {
var logId, createdAt, macAddress;
event.Records.forEach((record) => {
console.log('DynamoDB Record: %j', record.dynamodb);
logId = record.dynamodb.NewImage.logId.S;
console.log('logId: %j', record.dynamodb.NewImage.logId.S);
createdAt = record.dynamodb.NewImage.createdAt.N;
console.log('createdAt: %j', record.dynamodb.NewImage.createdAt.N);
macAddress = record.dynamodb.NewImage.macAddress.S;
console.log('macAddress: %j', record.dynamodb.NewImage.macAddress.S);
});
// 更新データチェック
if (!logId || !createdAt || !macAddress) {
context.fail('Updated data is invalid')
}
// myThings Developersへリクエスト
requestDevelopers(context);
};
/**
* "2016/12/21(水)"の形式の文字列を返す
* @return string 日付文字列
*/
function createPostTimeString() {
// 年
var year = date.getFullYear();
// 月
var month = date.getMonth() + 1;
if (month < 10) {
month = '0' + month;
}
// 日
var day = date.getDate();
if (day < 10) {
day = '0' + day;
}
// 曜日
var weekDayList = [ "日", "月", "火", "水", "木", "金", "土" ];
var weekDay = weekDayList[ date.getDay() ];
return year+"/"+month+"/"+day+"("+weekDay+")";
}
/**
* myThings Developersへのリクエスト
* @return void
*/
function requestDevelopers(context) {
// リクエストパラメータの生成
var postArgs = {
date: dateString
}
var postData = qs.stringify({
"entry": JSON.stringify(postArgs),
});
// リクエスト設定
var options = {
hostname: "mythings-developers.yahooapis.jp",
path: "/v2/services/<<個人のパス>>/mythings/<<個人のパス>>/run",
port: 443,
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
"Authorization": "Bearer " + accessToken,
},
};
// リクエスト
var req = https.request(options, function(res){
// 401のとき
if (res.statusCode == 401) {
// コールバック付きのrefreshAccessTokenを呼ぶ
refreshAccessToken(context);
return;
}
// レスポンス処理
res.on("data", function(body){
var parseData = JSON.parse(body);
if(typeof( parseData["flag"] ) != "undefined") {
context.succeed("カスタムトリガーの実行リクエストを受け付けました。")
} else {
console.log("カスタムトリガーの実行リクエストの受付に失敗しました。:"+body);
}
});
})
.on("error", function(res){
context.fail("カスタムトリガーの実行リクエストの受付に失敗しました。:"+res.content);
});
req.end(postData)
}
/**
* アクセストークンのリフレッシュ
*/
function refreshAccessToken(context) {
console.log("refreshAccessTokenにきたよ");
// リフレッシュ用データのセット
var reqData = qs.stringify({
"grant_type": "refresh_token",
"refresh_token": refreshToken
});
// リクエスト設定
var buffer = new Buffer(appid + ":" + secret, "ascii");
var options = {
hostname: "auth.login.yahoo.co.jp",
path: "/yconnect/v1/token",
port: 443,
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
"Authorization": "Basic " + buffer.toString("base64"),
}
};
// リクエスト実行
var req = https.request(options, function(res) {
console.log("refreshAccessTokenのrequestのなかにきたよ");
// 401の場合
if(res.statusCode == 401) {
context.fail("リフレッシュトークンの有効期限が切れました。myThings Developersのサンプルコードからリフレッシュトークンを再取得して下さい。");
} else if(res.statusCode != 200) {
context.fail("カスタムトリガーの実行リクエストの受付に失敗しました。:"+res.content);
}
// レスポンス処理
res.on('data', function(body){
var parseData = JSON.parse(body);
accessToken = parseData['access_token'];
requestDevelopers(context);
});
});
// POSTデータのリクエスト
req.end(reqData);
}
- ラズパイから再度下記のコマンドを打ち、Slackに投稿&メールが届いて入ればOK!!
$ mosquitto_pub -d -h beam.soracom.io -t topic/button -m '{"macAddress": "XX:XX:XX:XX:XX:XX"}' -p 1883
Client mosqpub/4091-darmaso-ra sending CONNECT
Client mosqpub/4091-darmaso-ra received CONNACK
Client mosqpub/4091-darmaso-ra sending PUBLISH (d0, q0, r0, m1, 'topic/button', ... (35 bytes))
Client mosqpub/4091-darmaso-ra sending DISCONNECT

ラズパイにスイッチをつける
-
下記のように配線 (スイッチを押している間のみLEDも光るようにしてみてあります)
※詳しくは、タクトスイッチを使ってLEDを点灯してみるをご参照下さいmm -
PahoのPythonクライアントであるpaho-mqttをインストール
$ sudo pip install paho-mqtt
Downloading/unpacking paho-mqtt
Downloading paho-mqtt-1.2.tar.gz (49kB): 49kB downloaded
Running setup.py (path:/tmp/pip-build-wI_jbk/paho-mqtt/setup.py) egg_info for package paho-mqtt
Installing collected packages: paho-mqtt
Running setup.py install for paho-mqtt
Successfully installed paho-mqtt
Cleaning up...
- ボタンの制御部分のソースコードは下記
#! /usr/bin/env python
import RPi.GPIO as GPIO
import time
import json
import paho.mqtt.client as mqtt
def on_connect(client, userdata, rc):
print("Connected with result code " + str(rc))
def on_disconnect(client, userdata, rc):
if rc != 0:
print("Unexpected disconnection.")
def on_publish(client, userdata, mid):
print("publish: {0}".format(mid))
def main():
# Setting GPIO
GPIO.cleanup()
GPIO.setmode(GPIO.BCM)
GPIO.setup(24, GPIO.IN)
GPIO.setup(25, GPIO.OUT)
# Setting MQTT
client = mqtt.Client()
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_publish = on_publish
client.connect("beam.soracom.io", 1883, 60)
try:
while True:
if ( GPIO.input(24) == GPIO.HIGH ):
GPIO.output(25, GPIO.HIGH)
message = json.dumps({"macAddress":"XX:XX:XX:XX:XX:XX"})
client.publish("topic/button", message)
else:
GPIO.output(25, GPIO.LOW)
time.sleep(0.5)
except KeyboardInterrupt:
GPIO.cleanup()
if __name__ == '__main__':
main()
- 下記のようにコマンドを実行し、タクトスイッチを押して、「publish: 1」と表示されていればOK
$ sudo python button.py
publish: 1
完成デモ
なんとか完成しました!!
最後に
ラズパイの準備から色々と大変でしたが、なんとか完成までこぎつけることができました。
ラズパイをいじるのはそれなりに知識がないと大変でしたが、
AWS側はサーバを用意しなくてもGUI上でポチポチしてコードがUploadするだけで十分なのは良いですね!!
ラズパイもdockerとかでイメージを管理してしまえば、構築に手間もかからないので、
今後はサーバレスアーキテクトとコンテナ管理をうまくやっていけばある程度のものはすぐにできてしまいそうな予感。
次はdockerでのラズパイ管理もチャレンジしてみたいと思いますmm
参照メモ
- Rule設定時の「Message Source」の項目メモ
項目 | 説明 | 備考 |
---|---|---|
Using SQL version | SQLバージョンの選択。選択しなくてもOK。 | デフォルト値がある模様。 |
Attribute | 対象とするデータ。 | 今回はすべてのデータなので「*」を利用。 |
Topic filter | 対象とするトピック。 | 今回は「topic/button」を指定する。全てのTopicに送信されたデータは「#」を指定。 |