16
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AWS IoTとESP32を利用してAlexaに(物理的に)電気のスイッチを操作してもらう

Posted at

はじめに

Amazon Echo買ったけど、使わずに結局放置している方多いんじゃないでしょうか??(私だけ?:thinking:
周りの人をみてみると、Alexaは家電の操作をメインの使い方にしている方は良く使っているみたいですね。
しかーし、わざわざAlexa対応家電なんか買ってるとお金が...:sob:

買う余裕がない⇨作ればいい

発想の転換というやつですかね。知らんけど。
ということでとりあえず電気のスイッチを押してもらうものを作りました。
せっかくなので作り方を共有します。

完成物

まずはじめに完成物をどうぞ
古いバージョンなのでマウンタがなく、完成のものとは少し見た目が違いますがこのような感じです。
IMG_20190315_162503.jpg
このように1台のESP32で2台のサーボを操作するものを作成していきます。

1台ですが動いている様子はこちら 1
ちょっと品質悪いですが我慢してください。

#環境
###プログラミング環境

  • Python 3.7
  • Arduino v1.8.8(ESP32へ書き込めるようセットアップ済みとする)

###使用機器・部品

  • ESP32
  • ブレッドボード
  • サーボモータ SG90 * 2個
  • 強力両面テープ

概要

今回のシステムの大まかな構成図はこんな感じです。

構成図.png

#作り方
かなり詳しく書くつもりなので、わかるところはどんどん読み飛ばしてください。
一部はAmazon公式チュートリアルの手順を参考にしています。ぜひこちらも参照してみてください。
##AWS IoT
今回、AWSIoTで使用する中心の機能として、MQTTプロトコルでの通信とshadow機能です。
MQTTとは、IoT向けのPubSub型軽量プロトコルとなっており、ざっくり説明すると中央で全てを管理しているサーバが存在し、指定したトピックに対して書き込みができたり、購読しているトピックに変更があると通知が来るような仕組みになっています。あまり詳しくはないので調べてください。
Shadow機能はClassmethodsさんの解説記事がわかりやすいです。
では早速セットアップしていきましょう。

###リージョンの設定
重要
AWSのリージョンを米国西部(オレゴン)に変更しましょう
私はこれで時間を無駄にしました:sob:
Lambda_Management_Console.png

AWS IoTでモノのポリシーを作成する

AWS IoT Coreを開いた後、左側のところから「安全性」⇨「ポリシー」と進み、「ポリシーの作成」をクリックしましょう。

ポリシーは以下の画像のように作成しましょう。
妥協した設定なので、詳しく設定可能な方は設定してください。
AWS_IoT.png

入力できると「作成」を押して作成しましょう。

AWS IoTにモノの登録をする

AWSIoTとESP32を紐付けるためにAWS IoTを使っていきます。
まずAWS IoT Coreを開き、左の「管理」⇨「モノの登録」⇨「単一のモノを作成する」の順に進みます。

この画面では名前のみ決め、次に進みます。
AWS_IoT.png
証明書を作成し、
AWS_IoT.png
証明書をダウンロードします。
また、必ず有効化を忘れないようにしましょう。
AWS_IoT.png
スクリーンショット_2019-06-22_4_16_40.png

##ESP32

ESP32とAWS IoTを接続する

####Arduinoに必要ライブラリを導入する
必要となるライブラリは

  • PubSubClient
  • ArduinoJson(version5系を入れてください)

どちらもライブラリマネージャから導入可能です。
導入方法はググってください。(手抜き:stuck_out_tongue_winking_eye:

スケッチを書き換える

まず、ここから私が作成したスケッチをダウンロードしてください。

10, 11行目にwifiの設定、14行目のendpointの設定、40行目、61行目の証明書の設定を書き換えましょう。
14行目のendpointの設定はAWS IoT Core⇨管理⇨先ほど作成したモノ⇨操作の中にあるRestAPIエンドポイントです。
40行目,61行目の証明書は先ほどダウンロードした中身をひたすらコピペしていきます。

書き換えが完了するとESP32に書き込んでみましょう。
シリアルモニタ.png
このようにPublishedが表示されるとOKです!
Error=-2が出た場合は証明書が間違ってる可能性が高いです。
Error=-1はAWSIoTで指定したポリシー違反を起こしていると思います。(今回は何も拒否していないので大丈夫だと思います)

私はWiFiが不安定な事象が発生したのですが、ボードマネージャからESP32用のボードをv1.0.2からv1.0.0にしたところ改善しました。

AWS Lambda

Lambdaいいですよね。
Lambdaをざっくり解説すると、何か実行するプログラムを書いておき、何かのサービスをトリガーにしてそのプログラムを実行してもらえるサービスです。
しかも、そのトリガーが発火してからサーバーが勝手に立ち上がり、処理が終わると勝手に死亡するのでサーバ管理が全く必要ありません。しかも時間課金で無料枠いっぱい。神かよ。
今回はAlexaからの入力をトリガーにしてAWSIoTへデータを流すor現在の状態をAlexaに返答するといったことに使います。

###Lambda関数作成
超重要
**リージョンをオレゴンに変えましょう。**東京で作るとAlexaConsoleで弾かれます。
これからの画像は東京リージョンになってるとこもありますが、気にしないでください。
Lambda_Management_Console.png

こんな感じで関数名だけ決めて作りましょう。言語はPython3.7を使用します。
スクリーンショット 2019-06-22 2.10.49.png

###Roleへのポリシーの追加
AWS IAM⇨ロールに移動し、先ほど自動で作成されたロールを探します。
関数名-role-*****のような形になっているはずです。
それをクリックすると次のページで「ポリシーをアタッチします」という青いボタンが出現するのでためらわず押しましょう。
次に上の方にある「ポリシーの作成」というボタンを押します。

JSONタブを選び、下のJSONファイルをコピペしましょう。
IAM_Management_Console.png

policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iot:GetThingShadow",
                "iot:Publish"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

そのあとはポリシーの確認⇨名前の設定⇨ポリシーの作成の順に行うと大丈夫です。
これでポリシーの作成は完了したため現在のタブを閉じ、「ポリシーをアタッチします」のボタンを押した後の画面に移動しましょう。
その後はこの画像の通り作成したポリシーを選択し、右下のポリシーのアタッチを行います。
IAM_Management_Console.png

これでポリシーの設定は完了です。

Lambda関数の中身を作る

ここからファイルをダウンロードしてください。
このコードはAmazon公式チュートリアルで配布されているコードをベースに改変しています。ありがとうございます。

ここでは変更する必要のあるファイルはconfig.jsonのみです。

config.json
{
    "DEVICES_CAPABILITY": [
        {
            "endpointId": "lightUp",
            "friendlyName": "上の電気(呼び出しに必要な名前)",
            "description": "説明(AlexaAppに表示)",
            "manufacturerName": "your name",
            "displayCategories": [
                "LIGHT"
            ],
            "cookie": {
                "extraDetail1": "optionalDetailForSkillAdapterToReferenceThisDevice"
            },
            "capabilities": [
                {
                    "type": "AlexaInterface",
                    "interface": "Alexa.PowerController",
                    "version": "3",
                    "properties": {
                        "supported": [{
                            "name": "powerState"
                        }],
                        "proactivelyReported": true,
                        "retrievable": true
                    }
                },
                {
                    "type": "AlexaInterface",
                    "interface": "Alexa",
                    "version": "3"
                }
            ]
        },
        {
            "endpointId": "lightDown",
            "friendlyName": "下の電気",
            "description": "部屋の下の電気",
            "manufacturerName": "your name",
            "displayCategories": [
                "LIGHT"
            ],
            "cookie": {
                "extraDetail1": "optionalDetailForSkillAdapterToReferenceThisDevice"
            },
            "capabilities": [
                {
                    "type": "AlexaInterface",
                    "interface": "Alexa.PowerController",
                    "version": "3",
                    "properties": {
                        "supported": [{
                            "name": "powerState"
                        }],
                        "proactivelyReported": true,
                        "retrievable": true
                    }
                },
                {
                    "type": "AlexaInterface",
                    "interface": "Alexa",
                    "version": "3"
                }
            ]
        }
    ],
    "thingsName": "powerPotchinThings"
}

DEVICE_CAPABILITYで定義するデバイスを書いていきます。
今回は1台のESP32で2つのスイッチを操作する予定なのでlightUp,lightDownの2つを定義しています。
(注:lightUp,lightDownの名前を変更するとArduinoのスケッチを変更する必要があります)
(注:サーボ1台でいい場合はlightDownのブロックを消すといいと思います。)
ここで変更するのはfriendlyName,description,manifactureName,thingsNameのところのみです。
frienlyNameはAlexaから呼び出す名前になるので、呼びやすい名前に変更してください。
thingsNameはAWSIoTで設定したモノの名前です。

discription,manifactureNameは適当で大丈夫。

コードの説明

飛ばしても大丈夫です。

まず、このLambdaに飛んでくるリクエストとして、デバイスを発見するための通信と、デバイスの状態を確認orデバイスの状態を更新させる通信があります。
デバイスを発見する通信は以下に挙げるhandle_discover_v3で処理しています。
といってもconfigのDEVICE_CAPAVILITYを読み込んで返却しているだけですね。

lambda_handler.py
# v3 handlers
def handle_discovery_v3(request):
    with open('config.json') as f:
        config = json.load(f)
        devices_capability = config['DEVICES_CAPABILITY']
    response = {
        "event": {
            "header": {
                "namespace": "Alexa.Discovery",
                "name": "Discover.Response",
                "payloadVersion": "3",
                "messageId": get_uuid()
            },
            "payload": {
                "endpoints": devices_capability
            }
        }
    }
    return response

こちらは発見のための通信ではないものが全て流れてきます。
重要なのがrequest_namespaceであり、Alexa.PowerControllerはデバイスの電源の状態を制御するために使われます。ここで詳しいドキュメントが見れます。
Alexaの場合はデバイスの状態を確認するために使われており、ここでドキュメントが見れます。
返却値はそちらを参照してもらうとして、デバイスの制御に重要なのはどのデバイスをON、OFFどちらにするのかであり、その値はrequest_device,valueに格納されています。それをAWSIoT側に渡していますね。

lambda_function.py
def handle_non_discovery_v3(request):
    request_namespace = request["directive"]["header"]["namespace"]
    request_name = request["directive"]["header"]["name"]
    request_device = request["directive"]["endpoint"]["endpointId"]

    if request_namespace == "Alexa.PowerController":
        if request_name == "TurnOn":
            value = "ON"
        else:
            value = "OFF"

        iot.publish(request_device, value)

        response = {
            "context": {
                "properties": [
                    {
                        "namespace": "Alexa.PowerController",
                        "name": "powerState",
                        "value": value,
                        "timeOfSample": get_utc_timestamp(),
                        "uncertaintyInMilliseconds": 500
                    }
                ]
            },
            "event": {
                "header": {
                    "namespace": "Alexa",
                    "name": "Response",
                    "payloadVersion": "3",
                    "messageId": get_uuid(),
                    "correlationToken": request["directive"]["header"]["correlationToken"]
                },
                "endpoint": {
                    "scope": {
                        "type": "BearerToken",
                        "token": "access-token-from-Amazon"
                    },
                    "endpointId": request_device
                },
                "payload": {}
            }
        }
        return response

デバイスの状態アップデートをAWSIoT側に渡すコードの中身はこのようになっています。

iot.py
# -*- coding: utf-8 -*-

import json
import boto3

iot = boto3.client('iot-data')
with open('config.json') as f:
    config = json.load(f)
    things_name = config['thingsName']
    
def publish(device_name, state):
    topic = f'$aws/things/{things_name}/shadow/update'
    payload = {
        "state": {
            "desired": {
            }
        }
    }
    payload["state"]["desired"][device_name] = state
    iot.publish(
        topic=topic,
        qos=0,
        payload=json.dumps(payload, ensure_ascii=False)
    )
    return True

このようなpayloadを作り、AWSIoT側に渡しています。

payload.json
{
    "state": {
        "desired": {
            "lightUp": "ON"
        }
    }
}

AWS IoTではモノから報告された状態と、desiredで報告された値に差が出るとdeltaトピックに差分を報告します。
ESP32ではそのトピックを購読してあるので差分を解消するようにデバイスを操作してあげればOKですね。

Lambdaにデプロイする

先ほどダウンロードし、一部書き換えたものをzipに圧縮しlambdaにアップロードします。
圧縮する際は下の画像のようにファイル、フォルダを全部選択して圧縮し、解答したときに余分なフォルダが出来ないようにしましょう。
スクリーンショット_2019-06-22_15_05_06.png

圧縮ができるとLambdaにアップロードします。
スクリーンショット_2019-06-22_15_33_52.png
こんな感じでデプロイしましょう。

すぐにこの画面を使うのでそのままにしておいてください。

Alexaスキル

Alexaに自作のスキルを登録していきます。
公式参考ページ

###Alexaスキル登録
まずはじめにAlexa開発者登録を行います。
こちらのページからいつも使用しているAmazon.co.jpのid,passでログインしましょう。
amazon.comのアカウントも所持している方はpassが同一だと日本のアカウントにログインできない問題が起こる可能性があるので、違うものに変更しておくことをお勧めします。

ログインできるとスキルの作成と進み、スキル名を入力し、スマートホームを選択します。
Alexa_Developer_Console.png
選択したらスキルの作成を押して次の画面に移動しましょう。

この画面ではまず、スキルIDをLambda関数側にコピーする必要があります。
68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3236323331342f66363565633761632d343232642d613461312d353164352d6533633631383162653862622e706e67.png
:arrow_up:AlexaConsoleの画面の囲っているIDを:arrow_down:のLambdaの画面に貼り付けましょう
Lambda_Management_Console.png
この様にコピペしてから右下の追加を押してから右上の保存を押してLambda関数を保存してください。

最後に、Lambdaの右上にあるARNをコピーしておきましょう。
Lambdaの画面↓
Lambda_Management_Console.png

そしてそれが終わると、コピーしたARNをAlexa側に登録します。
AlexaConsoleの画面↓
Alexa_Developer_Console.png

特にエラーなく完了すると大丈夫です!

###アカウントリンクの設定
次にアカウントリンクを設定します。
OAuth2.0を使ってアカウントとリンクさせる機能を提供する必要があるのですが、Login with AmazonというAmazonのアカウントを使ってログインするものがAmazonより提供されているのでそれを使う様にセットアップしていきます。
まず、Login with amazonのコンソールページに移動しましょう。
そこでセキュリティプロファイルの新規作成を行います。

  • セキュリティプロファイル名
  • セキュリティプロファイルの説明
  • プライバシー規約同意書URL

の入力欄があると思うのですが、適当に埋めましょう。外部に公開するわけではないので、規約同意書URLはexample.comなどで大丈夫です。

登録が完了すると:gear:マークからWeb設定に移動します。
そこでは許可された返信URLをAlexaConsole側のリダイレクト先のURIを設定し、AlexaConsole側の設定は以下の様に入力しましょう

設定項目
認証画面のURI https://www.amazon.com/ap/oa
アクセストークンのURI https://api.amazon.com/auth/o2/token
クライアント ID login with amazonに表示
クライアントシークレット login with amazonに表示
スコープ profile

alexa_account_link.png
保存を押して、正常に保存されるとOKです。

Alexaアプリ

ここからはAlexaアプリでスキルを有効化してデバイスの検索をしていきます。
スマートフォンにAlexaアプリを入れてることを前提とします。

Alexaスキルの有効化

Alexaアプリでスキルの画面に移動し、有効なスキル⇨開発⇨先ほど作成したスキル名に移動します。
有効にして使用するを押して手順に従うと、最終的にデバイスの検索が始まって2台のシーンが検出されるとOKです。
ここでログイン画面がでない場合はアカウントリンクの設定が怪しいです。
デバイスが検出されない場合はLambdaが怪しいです。

名前の変更について

Alexaアプリの最初の画面に戻り、右下のデバイスを選ぶと、照明のボタンが上に存在すると思うので押すと、config.jsonで設定したfriendlyNameの値が反映されているはずです。
名前を変更したい場合は一度デバイスをアプリで削除してからLambdaのfriendlyNameを更新してから新しいデバイスの追加を行うと新しい名前で見つけることができると思います。

###ESP32までデータが流れるか確認する
Alexaアプリからモノの状態を変更すると、Alexa->Lambda->AWS IoT->ESP32という順にデータが流れていきます。ということでAlexaアプリでON,OFFを切り替えてESP32のシリアルモニタにその情報が流れてきているか確認しましょう。

ON,OFFを切り替えたときにこのような表示があればOKです。

一部です
Received. topic=$aws/things/powerPotchinThings/shadow/update/delta
lightDown -> OFF

ここまで完成するとあとはハードウェア側のセットアップです。

##ESP32

###サーボモータをつなぐ

arduino.ino
#define LIGHT_UP_PIN 27
#define LIGHT_DOWN_PIN 25
#define BUTTON_PIN 0

こちらに定義したピンにpwm信号が流れるようになっています。なので、LIGHT_UP_PIN,LIGHT_DOWN_PINに指定したピン番号にサーボモータのオレンジの線が繋がるようにつなぎましょう。
また、BUTTON_PINはその場でスイッチを切り替えたい場合に使うスイッチ用のピンとなっています。外付けでプルアップ抵抗を繋ぐことを想定しています。(書き換えることのできる方は内臓プルアップに変更も可能です)
esp32-alexa.png

接続が完了するとボタンを押したりAlexaアプリからON,OFFを切り替えたりしてサーボモータが動くか確かめましょう。

##モーターを固定するマウンタを作る。
3Dプリンタを使って作ります。
ない方は買いましょう。
なくても両面テープでくっつけるだけでもなんとかなります。力が逃げてスイッチが押せなくなったりするので、何かしらの方法でマウンタを作ることをお勧めします。
Fusion360でサクッと作ったモデルがこちらから閲覧・DL可能です。使えそうなら使ってください。
使用例はこんな感じ
IMG_20190628_153719.jpg

##設置
ブレットボードをいい感じのところに貼り付けて、ESP32の電源を確保すると完成!!:heart_eyes: 2
IMG_20190628_172134.jpg

##モーターの角度を調整する
ArduinoのスケッチのswitchLightをいじってください。

arduino.ino
void switchLight(char* state, char* deviceName) {
  int ch;
  if(!strcmp(deviceName, "lightUp")) ch = 0;
  else if(!strcmp(deviceName, "lightDown")) ch = 1;
  
  if (!strcmp(state, "OFF")) {
    if(ch == 1){
      ledcWrite(ch, 80);<-これ
    }else{
      ledcWrite(ch, 80);<-これ
    }
    delay(150);
    ledcWrite(ch, 0);
  } else if (!strcmp(state, "ON")) {
    if(ch==1){
      ledcWrite(ch, 105);<-これ
    }else{
      ledcWrite(ch, 105);<-これ
    }
    delay(150);
    ledcWrite(ch, 0);
  }
}

#最後の独り言
やっと記事にできた。
どこまで詳しく書くべきなのか難しいですね。

ここ間違ってるよ!
こうする方がいいよ!といった指摘あれば大歓迎ですのでどしどしどうぞ。

そんなことより何かいいインターンないですかねぇー。
チーム開発を学びたい。

  1. 動画貼るためにわざわざTwitterを使うのどうにかならんのか。

  2. 設置場所の都合上サーボモータ1個です。

16
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?