概要
先日「Amazon Dash Button」がついに日本でも発売になりましたね。
これ、要はボタンをポチッと押すと対象の商品がAmazonで自動注文されて自宅に届くというものです。
今回はこれを自作しちゃおう!という旨の記事です。
わかる人はわかると思いますが、正確には「Amazon Dash Button」のプログラム可能版となる「AWS IoT Button」という方が近いと思います。
※話題にのってタイトルは「Amazon Dash Button」としてみました。
全体構成
全体のアーキテクチャは次の通りです。
- Raspberry Pi Zeroに取り付けたボタンを押すとMQTT経由でAWS IoTに接続します。
- AWS IoTのルールエンジンによりAWS Lambdaへメッセージをルーティングします。
- Lambda関数ではSelenium + PhantomJSよりAmazonのWebサイトをクローリングし、指定の商品を購入します。
処理の後ろから(3 -> 2 -> 1の順に)詳細に説明していきます。
1. Amazonで自動購入を行うLambda関数の作成
この章ではSeleniumとPhantomJSを利用してAmazonのWebサイトをクローリングし、指定の商品を自動購入するLambda関数を作成します。
AWS Lambdaとは?
http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html
AWS LambdaはコードをAWS Lambdaにアップロードすると、サービスがAWSインフラストラクチャを使用してコードの実行を代行するコンピューティングサービスとなります。
ランタイムにはJava, Node.js, Python, C#が選べますが、今回はPythonを利用します。
パッケージ構成
プロジェクトディレクトリ直下にLambda関数本体及び必要なライブラリを配置していきます。
$ tree -L 1
.
├── amzorderer.py # Lambda関数本体
├── phantomjs # PhantomJSバイナリ
├── selenium # Python用Seleniumライブラリ
└── selenium-3.0.2.dist-info
Selenium インストール
pipを利用してプロジェクトディレクトリ直下にSeleniumをインストールします。
pip install selenium -t /path/to/project-dir
PhantomJS インストール
PhantomJSの公式よりLinux 64bit版のtarをダウンロードし、binディレクトリ配下のphantomjsをプロジェクトディレクトリ直下に配置します。
Lambda関数の作成
ソースコードは以下でも公開しています。(追って公開予定)
# -*- coding:utf-8 -*-
__author__ = 'H.Takeda'
__version__ = '1.0.0'
import os
import boto3
from argparse import ArgumentParser
from base64 import b64decode
from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
# Amazon top page url.
AMAZON_URL = "https://www.amazon.co.jp"
# Amazon user id (email).
AMAZON_USER = boto3.client('kms').decrypt(
CiphertextBlob=b64decode(os.environ['user']))['Plaintext']
# Amazon user password.
AMAZON_PASS = boto3.client('kms').decrypt(
CiphertextBlob=b64decode(os.environ['password']))['Plaintext']
# User agent.
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/53 (KHTML, like Gecko) Chrome/15.0.87"
# Item dictionary.
ITEMS = {
"01": "1fLhF7q", # Toilet Paper
"02": "fhYcbp7" # Saran Wrap
}
def lambda_handler(event, context):
# Create web driver for PhantomJS.
dcap = dict(DesiredCapabilities.PHANTOMJS)
dcap["phantomjs.page.settings.userAgent"] = USER_AGENT
driver = webdriver.PhantomJS(desired_capabilities=dcap,
service_log_path=os.path.devnull,
executable_path="/var/task/phantomjs")
# Get amazon top page.
driver.get(AMAZON_URL)
# Transition to sign in page.
driver.find_element_by_id("nav-link-yourAccount").click()
# Input user id.
driver.find_element_by_id("ap_email").send_keys(AMAZON_USER)
# Input user password.
driver.find_element_by_id("ap_password").send_keys(AMAZON_PASS)
# Sign in.
driver.find_element_by_id("signInSubmit").click()
# Select item.
driver.get("http://amzn.asia/" + ITEMS[event["item"]])
# Add to cart.
driver.find_element_by_id("add-to-cart-button").click()
# Proceed to checkout.
driver.find_element_by_id("hlb-ptc-btn-native").click()
# Order.
# driver.find_element_by_name("placeYourOrder1")[0].click()
driver.save_screenshot("hoge.png")
driver.quit()
基本的にシンプルなSeleniumの操作を行なっているだけですが、ポイントをピックアップします。
- 関数のインプット(event)として購入する商品の区分値を受け取ります。
- ログインID(Email)、ログインパスワードはLambdaの環境変数から複合化して取得します。
- ユーザエージェントを設定しないとCookieを有効にしてくださいという旨のエラーが出てログインできません。
- PhantomJSのログ(
ghostdriver.log
)がデフォルトだとカレントディレクトリに作成されるので出力しない(/dev/null)ようにします。
※Lambda上で動かすと権限エラーでファイル作成できないため。
Lambda関数のデプロイ
アーカイブ
プロジェクトディレクトリをzip化します。
zipファイルの名称は適当で構いません。
$ zip -r upload.zip /path/to/project-dir/*
Lambda関数設定
AWSの管理コンソールからポチポチLambda関数の設定を行います。
作成したzipファイルのアップロードもここで行います。
- 環境変数に「user」と「password」を定義し、IAMにてあらかじめ作成しておいた暗号鍵(lambda/amzorderer)で暗号化します。
- IAMにてLambda関数に付与するロールはあらかじめ作成しておきます。
- メモリは128MBだと苦しかったので256MBにしています。
Lambda関数テスト
Actions > Configure test eventよりテストイベント(Lambda関数に引き渡すパラメータ)を設定し、
Testボタンを押下してLambda関数を実行します。
正常に処理が完了すればOKです。
ここまででAmazonで自動購入を行うLambda関数の作成は完了です。
2. AWS IoTの設定
この章ではAWS IoTを利用して、MQTTのリクエストを受付け、上で作成したLambda関数を呼出す設定を行います。
AWS IoTとは?
https://aws.amazon.com/jp/iot/how-it-works/
AWS IoTを利用することでさまざまなデバイスをAWSの各種サービスに接続し、データと通信を保護し、デバイスデータに対する処理やアクションを実行することが可能になります。
デバイスの登録
AWS IoTのコンソール画面よりAWS IoTに接続するデバイスの登録を行います。
- 「Get started」より登録画面へ進みます。
2. クライアントデバイスの環境情報を選択します。今回はRaspberry Pi Zero(OS:Raspbian)からPython SDKを利用してAWS IoTへ接続します。
3. 任意のデバイス名(ここではraspi0)を入力し「Next step」を押下します。
4. 「Liux/OSX」を押下して、公開鍵、秘密鍵、クライアント証明書をダウンロードしておきます(後で使います)。「Next step」を押下して次画面へ進みます。
5. デバイスの設定手順が出てくるので、「Done」を押下して完了となります。
ルールの登録
特定のトピックにメッセージがPublishされたら、Lambda関数を呼出すよう設定します。
- 「Create a rule」より登録画面へ進みます。
2. 任意のルール名(ここではamzorderer)を入力し、ルールクエリを設定します。
「amzordere」トピックにメッセージがPublishされた場合、item属性の値を抽出し、Actionを実行します。
3. ActionとしてLambda関数の呼出しを選択します。
4. 呼び出す関数は先ほど作成した「amzorderer」となります。
5. 「Create rule」を押下して作成完了となります。
3. クライアントデバイスの作成
この章ではRaspberry Piを利用してボタンを持つクライアントデバイスの作成していきます。
Raspberry Pi Zeroとは
言わずと知れたラズパイの小型モデルになります。
CPUクロック1GHz、メモリ512MBで約5ドルと破格の価格設定になってます。
今回はこのRaspberry Pi Zeroを利用して、
ボタンを押したらAWS IoTにリクエストを投げるデバイスを作っていきたいと思います。
必要なもの
どのご家庭にもある基本的な電子工作部品をつかって作っていきます。
- Raspberry Pi Zero
- GPIOピン
- ブレットボード
- 抵抗(10KΩ)
- タクトスイッチ
- ジャンパワイヤ(オス-メス)× 3
- 無線LAN子機
- はんだ(0.6mm推奨)
- miniUSB変換ケーブル
miniUSB の端子が一つしかないのでUSBハブがあると初期設定時は便利です。
Raspberry Pi の初期設定(OSインストール)
ここら辺を参考にRaspbian(Jessie)をインストールします。
無線LAN接続
今回はBuffaloの無線子機を利用してRaspberry Pi Zeroを無線LANに接続します。
USBに無線LAN子機を接続します。
lsusb
コマンドを打つと子機を認識しているのがわかりますね。
$ lsusb
Bus 001 Device 002: ID 0411:01a2 BUFFALO INC. (formerly MelCo., Inc.) WLI-UC-GNM Wireless LAN Adapter [Ralink RT8070]
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
wpa_passphrase
コマンドを使い、無線LAN接続に必要なSSID、パスワードを生成します。
$ wpa_passphrase [SSID] [パスフレーズ]
network={
ssid=[SSID]
#psk=[パスフレーズ] <- この行は削除して構いません
psk=[暗号化されたパスフレーズ]
}
上記のテキストをコピーし、/etc/wpa_supplicant/wpa_supplicant.conf
に追記します。
country=GB
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
ssid=[SSID]
psk=[暗号化されたパスフレーズ]
}
/etc/dhcpcd.conf
を編集し、固定IP化も行なっておきます。
IPアドレス、ルーター、DNSは適宜自分の環境に合わせて設定してください。
interface wlan0
static ip_address=192.168.11.30/24
static routers=192.168.11.1
static domain_name_servers=192.168.11.1
再起動して、母艦からSSH接続できれば設定完了です。
$ sudo shutdown -r now
GPIOのハンダ付け
他のモデルと違いRaspberry Pi ZeroでGPIOを使う場合はハンダ付けが必要となります。
思ったより細かい作業になるのでハンダの線形はφ0.6mmのものを使うと良いでしょう。
回路を組む
正直電子工作はど素人ですが、ネットで調べた情報を参考に組んでいきます。
Raspberry Pi ZeroのGPIOの配置は次のようになっています。
1番から3.3Vの電源をとり、GPIO25タクトスイッチのON/OFFはGPIO9番で受け取ります。
GPIO25とGNDの間に10kオームの抵抗を入れます。これはプルダウン抵抗と呼ばれHIGH(3.3V)かLOW(0V)信号を確実に伝える役割を担います。
画像が分かりづらくてすみません。
プログラムを作成する
回路が組めたのでタクトスイッチの入力を受取り、AWS IoTにメッセージをPublishするプロフラムを作成します。
ランタイム
Raspbian(Jessie)にはPython2.7がインスインストールされているので、Pythonでプログラムを書いていきます。
$ python -V
Python 2.7.9
必要なライブラリのインストール
AWS IoTに接続するためのPythonSDKが公開されているので利用します。
$ sudo pip install AWSIoTPythonSDK
実装
# -*- coding:utf-8 -*-
__author__ = 'H.Takeda'
__version__ = '1.0.0'
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
from argparse import ArgumentParser
import json
import logging
import RPi.GPIO as GPIO
import signal
import sys
import time
def configure_logging():
# Configure logging
logger = logging.getLogger("AWSIoTPythonSDK.core")
logger.setLevel(logging.DEBUG)
streamHandler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)
def parse():
argparser = ArgumentParser()
argparser.add_argument("-e", "--endpoint", type=str, required=True)
argparser.add_argument("-r", "--rootCA", type=str, required=True)
argparser.add_argument("-c", "--cert", type=str, required=True)
argparser.add_argument("-k", "--key", type=str, required=True)
args = argparser.parse_args()
return vars(args)
def careate_client(endpoint, root_ca, cert, private_pey):
# For certificate based connection.
client = AWSIoTMQTTClient("raspi0")
# Configurations
client.configureEndpoint(endpoint, 8883)
client.configureCredentials(root_ca, private_pey, cert)
client.configureOfflinePublishQueueing(1)
client.configureConnectDisconnectTimeout(10) # 10 sec
client.configureMQTTOperationTimeout(5) # 5 sec
return client
def handler(signum, frame):
print "Signal handler called with signal", signum
client.disconnect()
GPIO.cleanup()
sys.exit(0)
if __name__ == '__main__':
# Parse command-line arguments.
args = parse()
# Configure logging
configure_logging()
# Create mqtt client.
client = careate_client(
args["endpoint"], args["rootCA"], args["cert"], args["key"])
# Connect.
client.connect()
signal.signal(signal.SIGINT, handler)
GPIO.setmode(GPIO.BCM)
GPIO.setup(9, GPIO.IN)
before = 0
while True:
now = GPIO.input(9)
if before == 0 and now == 1:
# Create message.
message = {"item": "01"}
# Publish.
client.publish("amzorderer", json.dumps(message), 0)
print "message published."
time.sleep(0.1)
before = now
RPi.GPIO
を利用してGPIO9番に入力があった場合にAWS IoTにメッセージ(トイレットペーパーを購入するためのアイテム区分値「01」)をPublishしています。
動作確認
Rapsberry Pi Zero上で上記のスクリプトを実行し、動作確認してみます。
スクリプトの引数にAWS IoTのエンドポイント、ルートCA、クライアント証明書、秘密鍵のパスを指定します。
AWS IoT SDKのREADMEに記載されていますがルートCAはこちらから取得します。
クライアント証明書とは秘密鍵はAWS IoTの設定時にダウンロードしたzipに含まれています。
$ python test.py -e <endpoint> -r <rootCA path> -c <certificate path> -k <private key path>
2016-12-11 08:15:31,661 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Paho MQTT Client init.
2016-12-11 08:15:31,664 - AWSIoTPythonSDK.core.protocol.mqttCore - INFO - ClientID: raspi0
2016-12-11 08:15:31,667 - AWSIoTPythonSDK.core.protocol.mqttCore - INFO - Protocol: MQTTv3.1.1
2016-12-11 08:15:31,672 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Register Paho MQTT Client callbacks.
2016-12-11 08:15:31,675 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - mqttCore init.
2016-12-11 08:15:31,680 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Load CAFile from: root-CA.crt
2016-12-11 08:15:31,683 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Load Key from: raspi0.private.key
2016-12-11 08:15:31,687 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Load Cert from: raspi0.cert.pem
2016-12-11 08:15:31,691 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Custom setting for publish queueing: queueSize = 1
2016-12-11 08:15:31,696 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Custom setting for publish queueing: dropBehavior = Drop Newest
2016-12-11 08:15:31,699 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Set maximum connect/disconnect timeout to be 10 second.
2016-12-11 08:15:31,704 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Set maximum MQTT operation timeout to be 5 second
2016-12-11 08:15:31,710 - AWSIoTPythonSDK.core.protocol.mqttCore - INFO - Connection type: TLSv1.2 Mutual Authentication
2016-12-11 08:15:32,384 - AWSIoTPythonSDK.core.protocol.mqttCore - INFO - Connected to AWS IoT.
2016-12-11 08:15:32,386 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Connect time consumption: 70.0ms.
タクトスイッチを押すと、AWS IoTにメッセージがPublishされ無事にAmazonで「トイレットペーパー」の購入が完了していました。
やりたかったこと
-
クライアントデバイスの小型化
今回電源はモバイルバッテリーを利用していますが、リチウムポリマーを利用して小型化したい。ブレットボードも小さいものを利用したい。 -
複数ボタン対応
ボタン①を押したときはトイレットペーパー、ボタン②を押したときはサランラップを購入というように、複数ボタン設けたい。 -
ボタンのケース作成