【Python】AWS IoTボタンでランダムにチームを選びSlackに通知させてみた

## 1. はじめに
AWS IoTボタン単体でできることは限られています。

- SMSの送信
- Emailの送信
- Lambda関数の実行

SMSやEmailでは定型文を送るだけなのでつまらない。
もっと実用的なコンテンツは作れないかと考えた結果がこのまとめになります。

と思っていましたが、クリックには1Click、2Click、長押しの3パターンがあるようです。
これがあればもう少し幅が広がりそうですね。
[AWS IoT エンタープライズボタンでSlackに勤怠連絡する](https://qiita.com/hayao_k/items/0a8b3197ae7b2595ea10)

## 2.セットアップ
こちらのまとめを参考にしました。
[AWS IoT 1-Click と IoT エンタープライズボタンのセットアップ&動作確認](https://qiita.com/hayao_k/items/d36d4b2579d0bbbe21a9)

## やりたいこと
1. **チームは5つ**あり、**チームメンバーは最大5名**とする(前提)
2. **メンバー残数が0より多い**チームを**ランダムに1つ選ぶ**
3. 選んだチームのメンバー残数を**デクリメント**する
4. 選んだチーム名を**Slackに通知**する

## 3.構成イメージ
ざっくりこんなイメージです。
チーム名やメンバー残数を扱うため、DynamoDBを使います。
AWS IoTボタン単体でステータス管理はできないため、
DynamoDBにてステータスを管理するようにしました。
![Untitled (1).png](https://qiita-image-store.s3.amazonaws.com/0/187033/a5457dfc-340a-e44d-447c-b25a50eb8c5a.png)

## 4.実装
### ①テーブルデータ
DynamoDBのテーブルを作成し、以下のようなデータを投入します。

| id(String)|  stock(Integer) |
|:----------|----------------:|
| A         |               5 |
| B         |               5 |
| C         |               5 |
| D         |               5 |
| E         |               5 |

### ②ソース
言語は使ったことのないpythonを使いました。
*ソースコード基準がめちゃめちゃですみません。。*

```python:lambda_function.py
import random
import boto3
import requests
import json
from boto3.dynamodb.conditions import Key, Attr

# SlackWebHookの設定
SLACK_POST_URL = "URL"

dynamodb = boto3.resource("dynamodb")

def lambda_handler(event, context):
    message = ""
    #テーブル宣言
    table = dynamodb.Table("テーブル名")

    #フィルター条件
    fe = Key("stock").gt(0)

    #①seat_stockのレコード全件取得
    response = table.scan(FilterExpression=fe)
    items = response["Items"]

    #ストックありチーム数
    intTeamCount = len(items)

    #すべてのシートに残席がない場合(itemsのlengthが0)
    if intTeamCount == 0 :
        #メッセージに「在庫がありません。空いている席に着席してください。」を設定する
        message = "座席がありません。空いている席に着席してください。"
        requests.post(SLACK_POST_URL, data = json.dumps({"text": message}))
        return True
            
    #②チーム選び
    #①で取得したシート一覧からランダムでチームを選ぶ
    intTeamNumber = random.randrange(intTeamCount)

    #③残席数デクリメント
    #Stock数
    intStock = items[intTeamNumber].get("stock")

    #②で選んだチームのstockをデクリメントする
    intStock = intStock - 1

    #④レコード更新
    table.update_item(Key={"id": items[intTeamNumber].get("id")},
                  UpdateExpression="set stock = :s",
                  ExpressionAttributeValues={
                  ":s": intStock
                  },
                  ReturnValues="UPDATED_NEW"
                  )

    #⑤メッセージ設定
    #メッセ―ジに「あなたの座席は[②で選んだチーム名]です。」を設定する
    message = "あなたの座席は" + items[intTeamNumber].get("id") + "です。"

    #⑥slackにメッセージを送信
    requests.post(SLACK_POST_URL, data = json.dumps({"text": message}))
```

#### ③ソース解説
私がつまづいた部分を抜粋して解説します。

1.外部ライブラリの**random**と**boto3**と**requests**をインストール
 
- boto3はpythonでAWSを使用するためのライブラリ
    - ここではDynamoDBのためにインストールしました
- requestsはpythonでHTTP(S)通信を使用するためのライブラリ
    - ここではSlackのWebフックの為にインストールしました 

2.DynamoDBのレコード取得方法
やりたいこと#2の「メンバー残数が0より多いチーム」を実現のためDynamoDBからレコードを取得します。

DynamoDBのレコード取得方法は3種類あります。

- 主キーで取得する**get_item**
- SQLライクな**query**
- レコード全件取得の**scan**

queryが分かりにくかったので、**Scan**を使うことにしました。
公式の記事がこちら
レコード数が増えた場合はqueryを用いたほうが良さそうです。
>scan メソッドは、テーブル全体のすべての項目を読み込み、テーブルの全データを返します。オプションで filter_expression を提供して、条件に一致する項目だけが返されるように指定できます。ただし、テーブル全体がスキャンされた後にのみ、フィルタが適用されます。

```python
    #フィルター条件
    fe = Key("stock").gt(0)

    #①seat_stockのレコード全件取得
    response = table.scan(FilterExpression=fe)
    items = response["Items"]
```

`Key("stock").gt(0)`は「stockが0より多い」という条件です。
`FilterExpression`で、主キー以外の項目をフィルタリングできます。

フィルタリングのメソッドは以下のサイトを参考にしました。
[DynamoDB Conditions]
(http://boto3.readthedocs.io/en/latest/reference/customizations/dynamodb.html#dynamodb-conditions)

3.DynamoDBのレコード更新
やりたいこと#3「選ばれたチームはメンバー数をデクリメントする」を行った後、
DynamoDBのレコードを更新しなければならないので、**update_item**を使ってレコードを更新します。

```python
    #④レコード更新
    table.update_item(Key={"id": items[intTeamNumber].get("id")},
                  UpdateExpression="set stock = :s",
                  ExpressionAttributeValues={
                  ":s": intStock
                  },
                  ReturnValues="UPDATED_NEW"
                  )
```

`UpdateExpression`に更新する項目を指定します。
`:(コロン)`で変数を定義し、`ExpressionAttributeValues`で変数に値を関連付けることで
指定の項目を更新することができます。

`ReturnValues`は、UPDATED_NEWを設定すると更新後に影響のある項目を返却するそうです。
今回は特に使っていません。
詳しくは公式をご覧ください。
[DynamoDB での項目の操作](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/WorkingWithItems.html)

4.Slackに通知
やりたいこと#4「選んだチーム名をSlackに通知する」を実現するために
`requests.post`メソッドを使います。

```python
# SlackWebHookの設定
SLACK_POST_URL = "URL"

...

#⑥slackにメッセージを送信
    requests.post(SLACK_POST_URL, data = json.dumps({"text": message}))
```

SlackのWebhookのやり方は以下を参考にしました。
[SlackのWebhook URL取得手順](https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008)

## 4.Lambdaにデプロイ 
インストールしたライブラリと一緒にpythonファイルをzipにしてLambdaにデプロイします。

## 5.実行
無事成功！
レコードすべてのメンバー残数が0になると、
「座席がありません。空いている席に着席してください。」と表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/187033/4040ebf2-d1c0-4bab-8d89-e4997d835a42.png)

## 実際に使ってみた
このコンテンツを社内会議のチーム分けで実際に使いました。
Wi-Fiの回線速度の問題でターンアラウンドタイムが6,7秒になってしまい、大行列ができてしまいました。。。

また、チームをＡ～Ｇ、チーム数を5～7名に変更して行ったところ
Ａ～Ｅのチームの席は順調に埋まっていきましたが、ＦＧチームはだれも座りませんでした。
DynamoDBを調べたところ、ＦＧチームのチーム残数がString型になっており、
Scanの条件にヒットしておりませんでした。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/187033/abe71d32-782e-2ffd-bf20-672de2c26667.png)

チームを増やした時に、チーム残数をダブルコーテーションで囲っていたことを思い出し、自分の爪の甘さに絶句しました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/187033/bc1de58c-30f5-c868-277c-ae4e0be8bf72.png)

## 最後に
苦い思い出を作る結果となりましたが、
IoT 1-Clickはとても簡単にIoTサービスを開発できます。
定型メールであれば、10分15分程度でできると思います。

ぜひ、皆様も使ってみましょう。