M5Stackでスマートロックを作った

#今回作るモノ

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">サーボ交換してバッテリー積んで最終的にこんな感じ <a href="https://t.co/hG3dYQGKkA">pic.twitter.com/hG3dYQGKkA</a></p>&mdash; dai💸 (@dainbe1231) <a href="https://twitter.com/dainbe1231/status/1202474373707268099?ref_src=twsrc%5Etfw">December 5, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# はじめに
大まかな仕組み
![名称未設定ファイル (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296935/ebf59f8b-b169-08e0-7f38-08891c2b49b5.png)
API Gateway -> Lambda -> AWS IoT
といった形で繋げています。

**Q. 前にも作ってなかった？**
A. 作ってます。-> [コレ](https://qiita.com/dainbe/items/1b3a08b59d81afbefc4d)

前に作ったものは

* 基板丸出し
* 配線丸見え
* サーボの固定がお粗末
* ラズパイが扉についてるのは**カッコ悪い**

ということで作り直します。

# 使用したもの
* [M5Stack Gray] (https://www.switch-science.com/catalog/3648/) ※GrayでなくてもOK
* [M5Stack用プロトモジュール](https://www.switch-science.com/catalog/3650/)
* [SG92R](http://akizukidenshi.com/catalog/g/gM-08914/)
* [ネオジム磁石](https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-4VZZ)
* 2.54mmピッチ L字ピンヘッダ
* 電解コンデンサ　100µF 50V
* M3のネジと六角ナット
* M2のネジと六角ナット
* 3Dプリンター
* リチウムイオン電池(お好みで)

※ESP32をArduinoIDEで開発します。[公式のGitHub](https://github.com/m5stack/M5Stack/blob/master/docs/getting_started_ja.md)にセットアップ方法が載ってますので先にそちらを済ませてから

# 1.M5Stackでサーボを動かす
今回はESP32Servoを使って制御します。
:pushpin:[こちらの記事](https://qiita.com/n0bisuke/items/377a7c058e25e685d0a3)が参考になります。

## 回路を作る
サーボモータのSG92Rは小型のサーボなのでこのような回路にします。
![Untitled Sketch_ブレッドボード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296935/fc2d6272-affc-d068-8020-2ee3ac3d0bf8.png)

ESP32は起動時に1Aもの電流が流れ、サーボモーターと同時に使用するとマイコンの動作が不安定になる場合があるため申し訳程度にコンデンサを挟んでいます。

## サーボを動かす
次にプログラムの書き込みです。
以下にサンプルコードを載せます。

```C:Servo_test.ino
#include <M5Stack.h>
#include <ESP32Servo.h>

Servo servo1; // create four servo objects
int servo1Pin = 26;

// Published values for SG90 servos; adjust if needed
int minUs = 500;
int maxUs = 2400;

int pos = 0;      // position in degrees
bool lock_flag = true;

void door_open() {
  M5.Lcd.setTextFont(4);
  M5.Lcd.setTextSize(8);
  M5.Lcd.setCursor(80, 100, 1);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(GREEN, BLACK);
  M5.Lcd.println("open");
  servo1.attach(servo1Pin, minUs, maxUs);
  for (pos = 90; pos <= 180; pos += 1) {
    servo1.write(pos);
    delay(2);
  }
  delay(500);
  for (pos = 180; pos >= 90; pos -= 1) {
    servo1.write(pos);
    delay(2);
  }
  lock_flag = false;
  servo1.detach();
}

void door_close() {
  M5.Lcd.setTextFont(4);
  M5.Lcd.setTextSize(10);
  M5.Lcd.setCursor(60, 100, 1);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(RED, BLACK);
  M5.Lcd.println("close");
  servo1.attach(servo1Pin, minUs, maxUs);
  for (pos = 90; pos >= 0; pos -= 1) {
    servo1.write(pos);
    delay(2);
  }
  delay(500);
  for (pos = 0; pos <= 90; pos += 1) {
    servo1.write(pos);
    delay(2);
  }
  lock_flag = true;
  servo1.detach();
}

void setup() {
  Serial.begin(115200);
  M5.begin(true, false, true);
  M5.Lcd.clear(BLACK);
  servo1.setPeriodHertz(50);      // Standard 50hz servo
  door_close();
}

void loop() {
  M5.update();
  if (M5.BtnA.wasReleased() || M5.BtnB.wasReleased() || M5.BtnC.wasReleased()) {
    if (lock_flag) {
      door_open();
    }
    else {
      door_close();
    }
  }
}
```

M5Stackのボタンを押してみて右に90°左に90°と動けば成功です。
うまく動いたらM5Stack用の基板に回路を作ってしまいましょう。
また、door_open()とdoor_close()はお宅によって逆の可能性があるので適宜修正してください。

# 2.外装設計
とりあえず動くものができたのでモチベーションを上げる意味を込めて3D CADで設計をします。
私はFusion360を使って設計しました。
とはいえ、ここで説明できないのでリンクを置いときます。

![M5Lock_img.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296935/d9f4ed78-8964-f44a-f0ad-255cff0b81c6.png)

* [本体パーツ](https://a360.co/2DEJM9M)
* [台座パーツ](https://a360.co/2DGTaKf)
* [底面パーツ](https://a360.co/2P9WMcT)
* [サーボホーン連結部](https://a360.co/2DGIPO6)
* [本体カバーパーツ](https://a360.co/2DKpWtY)
* [調整用スペーサー(5mm)](https://a360.co/2RgviVF)
* [位置調整パーツ](https://a360.co/2OJn5aN)

※位置調整パーツはなくても構いませんがあった方が設置するときに楽です。

ご家庭の3Dプリンターもしくは、どこかに頼んで出力しましょう。
私が使ったものは2万円の中華製のものです。

パーツができたら組み立てます。

せっかく3Dプリンターで出力しているので、お宅に合わせて修正や改善をしてください。

# 3.AWS IoTにつなげる
外装ができてモチベーションを上がったはずなので、次はAWS IoTにM5Stackを接続したいと思います。

ESP32(M5Stack)をAWS IoTに接続している記事はたくさんあるので:pushpin:[こちらの記事](https://blog.maripo.org/2017/07/esp32-aws-iot/)を参考にしながらPubSubClientなどの設定をしてください。

また、[ArduinoJson](https://arduinojson.org/v6/api/jsonobject/)も使用するのでIDEからダウンロードをします。

今回使用するコードは[GitHub](https://github.com/dainbe/M5Lock)にあげてあります。

send_status()で/shadow/updateに鍵の開閉を送信していて、開け閉めと60秒に1回送信しています。
また、/shadow/update/deltaの変更を受け取って関数を発火しています。

なのでshadowをupdateしてあげれば鍵の開け閉めができるということになります。

実際にAWS IoTのshadowをいじってみましょう。
AWS IoT -> 管理 -> モノ -> 作成したもの -> シャドウ
です。

ここでシャドウドキュメントを編集してみてM5Stackの画面が変わったらうまく接続できています。

何かエラーが発生して動かない場合は[トラブルシューティング](https://blog.maripo.org/2017/07/esp32-aws-iot-troubleshooting/)を参照してください。

# 4.LambdaとAWS IoTをつなげる
次にLambdaとAWS IoTを接続します。

## ロール作成
まず最初にロール作成をします。

AWS IAMにおいて、ロールを設定して、Lambdaのインスタンスに割り当てます。
そこでポリシーを　

* AWSIoTDataAccess

を追加して保存します。
今回はUpdateしかしないのでここまでの権限は必要ないですが大は小を兼ねるの精神で見逃してください。

## インスタンス作成
Lambdaでインスタンスを作成します。
「名前」をよしなに設定して
「ランタイム」はPython 3.6を、
「ロール」は先ほど作ったものにします。

## Lambdaの設定
今回はコードをインラインで編集します。

```python:lambda_function
# coding: utf-8
 
import json
import boto3
 
# Functionのロードをログに出力
print('Loading function')  
 
#AWS IoT Data Planeオブジェクトを取得
iot = boto3.client('iot-data')
 
def lambda_handler(event, context):
    
    lock_state = event['lock_state']
    
    payload = {
        "state":{
            "desired": {
                "lock_state": lock_state
            }
        }
    }
    
    try:
        # メッセージをPublish
        response = iot.update_thing_shadow(thingName = '<YOUR THING>', payload = json.dumps(payload, ensure_ascii=False))
        
        print(response)
    
        return {
            'isBase64Encoded': False,
            'statusCode': 200,
            'headers': {},
            'body': {"lock_state": lock_state}
        }
        
    except Exception as e:
        print(e)
        return {
            'isBase64Encoded': False,
            'statusCode': 200,
            'headers': {},
            'body': {"message": "failed"}
        }
```
こんな感じで編集します。

関数が発火するとeventに辞書型でjsonが渡されるので中身に応じてboto3のupdate_thing_shadow関数で状態を更新します。

## テスト
右上のテストのボタンから
![test_event.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296935/18071ab6-b4be-3aee-e2fe-049b85e1ed62.png)
こんなものを作ってテストしてみましょう。
AWS IoTのシャドウが更新されているはずです。

---
Slackなどに通知するコードなどを書いてもいいかもしれません。

# 5.API GatewayとLambdaをつなげる
次はAPI GatewayとLambdaをつなげます。
## API Gateway作成
AWSマネジメントコンソールよりAPI Gatewayを選択し、APIの作成行います。
APIの名前は好きなもので構いません。

## メソッドの作成
API Gatewayがリクエストを受け付けるhメソッドを定義します。今回はPOSTリクエストを受けた時にLambdaを起動するようにします。

アクション -> メソッドの作成　-> 「POST」を選択

統合タイプは「Lambda関数」を選択して
先ほど作った関数を指定します。

また、API GatewayからのLambda関数呼び出しを許可します。

## テスト
「テスト」ボタンからテストをします。

「リクエスト本文」に
`{"lock_state": 1}`
を入れて「:zap:テスト」を押します。

右側にログが出たら、LambdaとAWS IoTを確認してlock_stateが変更されているか確認してください。

## APIのデプロイ
テストを実行して変更が確認出来たらAPIをデプロイします。
「アクション」-> 「APIのデプロイ」
ステージ名を適当に設定し「デプロイ」ボタンを押します。

URLが表示されたら成功です。

後は好きな端末でPOSTしてあげれば鍵を操作できます。

# おまけ
私はiPhone (iOS12以降)を使っているのでこのように「ショートカット」を使って鍵を操作しています。
<img width="375" alt="iOS の画像.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/296935/c52e7a72-a736-4cd5-d77d-7673b7730ec0.jpeg">

「URL」に先ほどのURLをいれて「URLの内容を取得」でPOSTを選択しフィールドを真偽値にしてあげればiPhoneから操作ができます。