AWS IoT Coreとエッジデバイスの間でメッセージを送受信する

# はじめに
AWS IoT Coreにエッジデバイスを登録して、AWS IoT Coreとエッジデバイスの間でメッセージを送受信する方法を紹介します。
エッジデバイスにランタイムをインストールすることなく、手軽にメッセージの送受信を行うことができます。
（エッジデバイスにAWS IoT Greengrass(V1)をインストールしてAWSと接続する方法は[こちら](https://qiita.com/Yokogawa_Ishino/items/1fb734f091e952a16fea)をご覧ください。）
# 環境
動作確認済デバイス（OS）

- e-RT3 Plus F3RP70-2L[^ert3]（Ubuntu 18.04 32bit）
- Raspberry Pi 4 Model B （Ubuntu Server 20.04 32bit）

これらのデバイスでは armhf アーキテクチャのパッケージが動作します。

# エッジデバイスのクラウドへの登録
## ポリシーの作成
デバイス-クラウド間でのメッセージの送受信を許可するポリシーを作成します。

左上のサービスメニューから「IoT Core」→「安全性」→「ポリシー」へ進み、「作成」をクリックします。
![awspubsub-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/6b580ada-0640-0bec-a0a5-f4f9f5159086.png)

![awspubsub-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/1b9d77be-51f4-b56d-8576-eef95bfd3208.png)

    
任意のポリシー名を入力し、「アドバンストモード」をクリックして以下の内容を入力します。`<region>`, `<account>`はリージョン名とアカウント名で置き換えてください。

入力が完了したら「作成」をクリックします。
![awspubsub-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/eb5fa78b-7d9a-897e-cfd5-02e0d10f7154.png)

```
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish",
        "iot:Receive"
      ],
      "Resource": [
        "arn:aws:iot:<region>:<account>:topic/test/topic"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Subscribe"
      ],
      "Resource": [
        "arn:aws:iot:<region>:<account>:topicfilter/test/topic"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect"
      ],
      "Resource": [
        "arn:aws:iot:<region>:<account>:client/test-*"
      ]
    }
  ]
}
```
 > **Note**
> リージョン名は右上のメニューで、アカウント名は[こちらの手順](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/console_account-alias.html)で確認できます。
> ![awspubsub-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/f6e32bf9-1844-2977-ef2f-5156207e58ea.png)

## エッジデバイスの登録
AWS IoT Coreのモノにエッジデバイスを登録します。

「IoT Core」→「管理」→「モノ」へ進み、「モノを作成」をクリックします。
![awspubsub-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/519eca6b-6683-eb5b-99a3-9d1d96cc8cf6.png)


以下の設定でモノを作成します。

| 設定項目         | 設定内容                     |
| ---------------- | ---------------------------- |
| 作成するモノの数 | 1つのモノを作成              |
| モノの名前       | 任意（この例では「testdevice」） |
| デバイス証明書   | 新しい証明書を自動生成       |                 |                              |

ポリシーの設定では、[ポリシーの作成](#ポリシーの作成)で作成したポリシーを選択し「モノを作成」をクリックします。
![awspubsub-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/d14c0c43-1688-dff4-955e-71aad34c9666.png)


証明書やキーファイルをダウンロードして「完了」をクリックします。
![awspubsub-8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/dfb7d0eb-6913-55f3-7ae7-1b644624c6fe.png)


# メッセージの送受信
## エッジデバイスの準備
1. パッケージのインストール
    必要なパッケージをインストールします。

    ```bash
    sudo apt update
    sudo apt install cmake python3-dev python3-venv
    ```

    **Raspberry Pi**の場合、デフォルトのコンパイラだとSDKのインストールができないため以下のパッケージもインストールします。

    ```bash
    sudo apt install build-essential
    sudo apt install gcc-8
    ```

    >**Note**
    >エッジデバイスがproxy環境下にある場合は[proxy設定](https://qiita.com/Yokogawa_Mita/items/f7996d539b29618d7159#proxy%E8%A8%AD%E5%AE%9A)が必要です。

1. 仮想環境の準備とSDKのインストール
    仮想環境を作成します。

    ```bash
    python3 -m venv venv
    source venv/bin/activate
    ```

    以下のコマンドでSDKをインストールします。
    **e-RT3**の場合

    ```bash
    (venv) username@ubuntu:~$ python3 -m pip install wheel
    (venv) username@ubuntu:~$ python3 -m pip install awsiotsdk
    ```

    **Raspberry Pi**の場合
    コンパイラを指定してawsiotsdkをインストールします。

    ```bash
    (venv) username@ubuntu:~$ python3 -m pip install wheel 
    (venv) username@ubuntu:~$ CC=/path/to/gcc-8 python3 -m pip install awsiotsdk
    ```

    >**Note**
    >gcc-8のパスは以下のコマンドで確認できます。
    >
    >```bash
    >username@ubuntu:~$ which gcc-8
    >/usr/bin/gcc-8
    >```

    >**Note**
    >インストール中にエンコード関連のエラーが出た場合は、ロケールを指定して実行してください。
    >実行例
    >
    >```bash
    >(venv) username@ubuntu:~$ LC_CTYPE=C.UTF-8 python3 -m pip install awsiotsdk
    >```
    >

1. サンプルプログラムの準備
    AWSのSDK[^sdk]のサンプルプログラムである[pubsub.py](https://github.com/aws/aws-iot-device-sdk-python-v2/blob/v1.7.1/samples/pubsub.py)をデバイスの任意の場所に保存します（ライセンスは[こちら](https://github.com/aws/aws-iot-device-sdk-python-v2/blob/v1.7.1/LICENSE)）。

    > **Note**
    > 本記事ではタグ「v1.7.1」のコードを使用しています。
    > 

    [エッジデバイスの登録](#エッジデバイスの登録)でダウンロードした証明書やキーファイルをサンプルプログラムと同一のディレクトリに置きます。

## メッセージのやり取り
以下のコマンドでサンプルプログラムを起動します。引数にはエンドポイント、[エッジデバイスの登録](#エッジデバイスの登録)でダウンロードした証明書、キーファイルを設定します。

```bash
(venv) username@ubuntu:~$ python3 pubsub.py --endpoint xxx.amazonaws.com --root-ca AmazonRootCA1.pem --cert xxx-certificate.pem.crt --key xxx-private.pem.key
```

>**Note**
>エンドポイントは「IoT Core」→「設定」→「デバイスデータエンドポイント」で確認できます。
>![awspubsub-9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/bb9007c7-9b14-5621-8eee-75aea5c34381.png)

>**Note**
>エッジデバイスがproxy環境下にある場合は追加のオプションが必要です。詳しくは[proxy設定](#proxy設定)をご覧ください。


以下のような出力があれば成功です。

```bash
Connecting to xxx.amazonaws.com with client ID 'test-xxx'...
Connected!
Subscribing to topic 'test/topic'...
Subscribed with QoS.AT_LEAST_ONCE
Sending 10 message(s)
Publishing message to topic 'test/topic': Hello World! [1]
Received message from topic 'test/topic': b'"Hello World! [1]"'
Publishing message to topic 'test/topic': Hello World! [2]
Received message from topic 'test/topic': b'"Hello World! [2]"'
Publishing message to topic 'test/topic': Hello World! [3]
...
```

このプログラムは`test/topic`トピックにメッセージをpublishするとともに、`test/topic`トピックをsubscribeし、10回メッセージを送受信すると終了します。
したがって、自分が送信したメッセージを自分で受信するという動きを10回繰り返して終了します。

### D2Cメッセージの確認
クラウドにメッセージが到着していることを確認するには「IoT Core」→「テスト」→「MQTTテストクライアント」へ進み、`test/topic`トピックをsubscribeします。
メッセージを受信すると画面下部に受信したメッセージが表示されます。
![awspubsub-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/792dbea0-1341-ee88-2d75-e15da1a90f16.png)


### C2Dメッセージの確認
以下のコマンドで、メッセージをpublishしない設定でプログラムを起動します。

```bash
(venv) username@ubuntu:~$ python3 pubsub.py --endpoint xxx.amazonaws.com --root-ca AmazonRootCA1.pem --cert xxx-certificate.pem.crt --key xxx-private.pem.key --message ""
```

クラウドからメッセージを送信するには「トピックに公開する」から`test/topic`トピックにメッセージをpublishします。
![awspubsub-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/682603/b4c9ea92-4051-15d9-cef5-d12997512d94.png)


エッジデバイスに以下のような出力があれば成功です。10回メッセージを受信するとプログラムが終了します。

```bash
...
Received message from topic 'test/topic': b'{\n  "message": "This is a message from AWS"\n}'
...
```

# 補足
## proxy設定
エッジデバイスがproxy環境下にある場合は以下のコマンドでプログラムを実行します。

```bash
(venv) username@ubuntu:~$ python3 pubsub.py --endpoint xxx.amazonaws.com --root-ca AmazonRootCA1.pem --cert xxx-certificate.pem.crt --key xxx-private.pem.key --proxy-host "proxy.example.com" --proxy-port xx
```

ユーザー名とパスワードが必要な場合は、プログラムの以下の箇所を書き換えます。
変更前

```python:pubsub.py
    if (args.proxy_host): 
        proxy_options = http.HttpProxyOptions(host_name=args.proxy_host, port=args.proxy_port) 
```

変更後

```python:pubsub.py
    if (args.proxy_host):
        proxy_options = http.HttpProxyOptions(host_name=args.proxy_host, port=args.proxy_port, auth_type = http.HttpProxyAuthenticationType.Basic, auth_username="<username>", auth_password="<password>")
```


[^ert3]: [産業用AIプラットフォーム | 横河電機株式会社](https://www.yokogawa.co.jp/solutions/products-platforms/control-system/real-time-os-controller/ert3-ai-platform/)
[^sdk]: [GitHub - aws/aws-iot-device-sdk-python-v2: Next generation AWS IoT Client SDK for Python using the AWS Common Runtime](https://github.com/aws/aws-iot-device-sdk-python-v2)