[PYTHON] "Amazon Dash Button" has landed in Japan, but I dared to make it myself

DashButtonCover.jpg

Overview

The other day, "Amazon Dash Button" has finally been released in Japan. The point is that when you click the button, the target product is automatically ordered by Amazon and delivered to your home. Let's make this ourselves this time! It is an article to that effect.

Those who understand it will understand, but to be exact, those who say "AWS IoT Button" which is a programmable version of "Amazon Dash Button" I think it's close.

overall structure

The overall architecture is as follows:

全体構成.png

  1. Press the button attached to the Raspberry Pi Zero to connect to AWS IoT via MQTT.
  2. The AWS IoT rules engine routes messages to AWS Lambda.
  3. The Lambda function crawls the Amazon website from Selenium + PhantomJS and purchases the specified product.

I will explain in detail from the back of the process (in the order of 3-> 2-> 1).

1. Create a Lambda function that makes an automatic purchase on Amazon

In this chapter, we will use Selenium and PhantomJS to crawl Amazon's website and create a Lambda function that automatically purchases the specified item.

What is AWS Lambda?

http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html When AWS Lambda uploads your code to AWS Lambda, the service becomes a computing service that uses your AWS infrastructure to take over the execution of your code.

Java, Node.js, Python, C # can be selected for the runtime, but this time we will use Python.

Package configuration

Place the Lambda function body and necessary libraries directly under the project directory.

$ tree -L 1
.
├── amzorderer.py				#Lambda function body
├── phantomjs					#PhantomJS binary
├── selenium					#Selenium library for Python
└── selenium-3.0.2.dist-info

Reference: https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html

Selenium installation

Install Selenium directly under the project directory using pip.

pip install selenium -t /path/to/project-dir

PhantomJS installation

Download the Linux 64bit version tar from the Official of PhantomJS, and place phantomjs under the bin directory directly under the project directory.

Creating a Lambda function

The source code is also available below. (Scheduled to be released later)

amzorderer.py


# -*- 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()

Basically, I'm just doing a simple Selenium operation, but I will pick up the points.

Deploy Lambda functions

archive

Zip the project directory. The name of the zip file does not matter.

$ zip -r upload.zip /path/to/project-dir/*

Lambda function settings

Set the Lambda function from the AWS management console. You can also upload the created zip file here.

Lambda Management Console.png

Lambda function test

Set the test event (parameter to be passed to the Lambda function) from Actions> Configure test event, Press the Test button to execute the Lambda function.

Lambda Management Console2.png

It is OK if the process is completed normally. This completes the creation of the Lambda function that automatically purchases on Amazon.

2. AWS IoT settings

In this chapter, AWS IoT is used to accept MQTT requests and configure settings to call the Lambda function created above.

What is AWS IoT?

https://aws.amazon.com/jp/iot/how-it-works/ By using AWS IoT, it is possible to connect various devices to various AWS services, protect data and communication, and perform processing and actions on device data.

Device registration

Register the device that connects to AWS IoT from the AWS IoT console screen.

  1. Proceed to the registration screen from "Get started".

AWS IoT0.png   2. Select the environment information for the client device. This time, we will connect to AWS IoT from Raspberry Pi Zero (OS: Raspbian) using the Python SDK.

AWS IoT4.png   3. Enter an arbitrary device name (raspi0 in this case) and press "Next step".

AWS IoT5.png   4. Press "Liux / OS X" to download the public key, private key, and client certificate (you will use them later). Click "Next step" to proceed to the next screen.

AWS IoT7.png   5. The device setting procedure will appear, so press "Done" to complete.

Rule registration

Set your Lambda function to be called when a message is published to a particular topic.

  1. Proceed to the registration screen from "Create a rule".

AWS IoT R1.png   2. Enter an arbitrary rule name (amzorderer in this case) to set the rule query. When the message is published to the "amzordere" topic, extract the value of the item attribute and execute the Action.

AWS IoT R2.png   3. Select Call Lambda function as Action.

AWS IoT3.png   4. The function to call is the "amzorderer" created earlier.

AWS IoT R4.png   5. Click "Create rule" to complete the creation.

AWS IoT R.png

3. Create client device

In this chapter, we will use Raspberry Pi to create a client device with buttons.

What is Raspberry Pi Zero?

Needless to say, it will be a small model of the well-known Raspberry Pi. It has an exceptional price of about $ 5 with a CPU clock of 1GHz and memory of 512MB. This time, using this Raspberry Pi Zero, I would like to create a device that sends a request to AWS IoT at the push of a button.

Things necessary

We will make it using basic electronic work parts that are found in every home.

IMG_0307.JPG

Since there is only one miniUSB terminal, it is convenient to have a USB hub at the time of initial setting.

Initial setting of Raspberry Pi (OS installation)

Install Raspbian (Jessie) by referring to Around here.

Wifi connection

This time, I will connect the Raspberry Pi Zero to the wireless LAN using Buffalo's wireless slave unit.

Connect the wireless LAN slave unit to USB. If you type the lsusb command, you can see that it recognizes the slave unit.

$ 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

Use the wpa_passphrase command to generate the SSID and password required for wireless LAN connection.

$ wpa_passphrase [SSID] [Passphrase]
network={
        ssid=[SSID]
        #psk=[Passphrase] <-You can delete this line
        psk=[Encrypted passphrase]
}

Copy the above text and add it to /etc/wpa_supplicant/wpa_supplicant.conf.

/etc/wpa_supplicant/wpa_supplicant.conf


country=GB
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
        ssid=[SSID]
        psk=[Encrypted passphrase]
}

Edit /etc/dhcpcd.conf and make it a fixed IP. Set the IP address, router, and DNS according to your environment.

interface wlan0
static ip_address=192.168.11.30/24
static routers=192.168.11.1
static domain_name_servers=192.168.11.1

If you can restart and SSH connection from the mother ship, the setting is completed.

$ sudo shutdown -r now

GPIO soldering

Unlike other models, soldering is required when using GPIO with Raspberry Pi Zero. It will be a finer work than I expected, so it is better to use a solder alignment of φ0.6 mm.

IMG_0309.JPG

Build a circuit

Honestly, electronic work is an amateur, but I will build it with reference to the information I searched on the net. The GPIO layout of Raspberry Pi Zero is as follows.

Raspberry-Pi-Model-Zero-Mini-PC.jpg

The power supply of 3.3V is taken from No. 1, and ON / OFF of GPIO25 tact switch is received by GPIO9. Insert a 10k ohm resistor between GPIO25 and GND. This is called a pull-down resistor and plays a role in reliably transmitting a HIGH (3.3V) or LOW (0V) signal. I'm sorry that the image is difficult to understand.

S__2564101.jpg

Create a program

Now that the circuit is assembled, we will create a program that receives the input of the tact switch and publishes the message to AWS IoT.

runtime

Python 2.7 is installed on Raspbian (Jessie), so I will write the program in Python.

$ python -V
Python 2.7.9

Installation of required libraries

The Python SDK for connecting to AWS IoT is open to the public, so use it.

$ sudo pip install AWSIoTPythonSDK

Implementation

publisher.py


# -*- 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

We publish a message (item classification value "01" for purchasing toilet paper) to AWS IoT when GPIO 9 is entered using RPi.GPIO.

Operation check

Execute the above script on Rapsberry Pi Zero and check the operation. Specify the AWS IoT endpoint, root CA, client certificate, and private key path as arguments to the script. Although it is described in the README of AWS IoT SDK, the root CA is here. Get from /content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem). What is a client certificate? The private key is included in the zip that you downloaded when you set up AWS IoT.

$ 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.

When I pressed the tact switch, a message was published to AWS IoT and the purchase of "toilet paper" was successfully completed on Amazon.

What I wanted to do

Recommended Posts

"Amazon Dash Button" has landed in Japan, but I dared to make it myself
When I tried to make a VPC with AWS CDK but couldn't make it
I tried my best to make an optimization function, but it didn't work.
I made an Amazon Web Service Dash button
I defined ForeignKey to CustomUser specified in AUTH_USER_MODEL in Django, but it is not referenced
I want to transition with a button in flask
How to use Decorator in Django and how to make it
I was able to repeat it in Python: lambda
I introduced black to vscode, but it doesn't autoformat