Raspberry Pi内の画像をbase64でエンコードして→AWS IoT→S3に保存

`カメラ` などで撮影した画像を `AWS IoT` 経由で `S3` に保存してみたくて試してみました。
今回はホストPCからRaspberry Piにアップした画像を使っています。
プログラムはサンプルプログラムを使用しています。
まだAWSについて知識が浅いので、コメントいただけるとありがたいです！

【追記】
ちなみに、base64エンコードしなくてもバイナリデータのままで送信してS3に保存できます。
その方が、S3からダウンロードしてそのまま画像がみれます。

こちら参考にさせていただきました！

-  [AWS IoT Device SDK for PythonをRaspberryPiで動かす](https://qiita.com/YamauchiHideki/items/13e1e6a28d4b71f7d159)
- [バイナリのデータをAWS IoT上でBase64エンコード＆JSON化して、後方サービスへ流す（例えばAWS Lambdaとか）](https://qiita.com/ma2shita/items/f7cbc395464c483e232d)

# 前提など

## 制約事項

参考の方にも記載がありますが、AWS IoTとの1回の送信は **最大128KBの制約** ありますので、注意してください！
[AWS IoTの制限](http://docs.aws.amazon.com/ja_jp/general/latest/gr/aws_service_limits.html#limits_iot)

【2018/01/19 追記】
128KB超えデータのアップロードについては、ラージアップロードパターンというものを使用するとよさそうです。
これはアップロードする前に毎回、AWS IoT、STSを使用してトークンを取得し、そのトークンを使用してS3へデバイスから直接アップロードするパターンです。
これにより、アクセスキーやシークレットキーをデバイス側に持たなくて済み、AWS IoTの証明書のみでアクセス管理ができる。
参考URL：[AWSIoT ラージデータアップロードパターン実装](https://qiita.com/haiiro-shimeji/items/cc172172bdd041dbbbc4)

## AWS IoTを使用することのメリット

【2018/01/19 追記】
あくまでも個人的な意見ですが、

- AWS IoTをデバイスのIFにして、いくつもAWSへの送信パターン（※）がある場合
- AWS IoTに登録されている証明書のみでアクセスができる（キーをいくつも管理しなくてもよくなる）

もし、デバイスからAWSへのアクセス権をなくしたい場合（デバイスの盗難など）、AWSコンソールからAWS IoTで管理している証明書を無効にするだけで、アクセス権をなくせます（デメリットにもなくかもですが・・・）。

戻す場合は、証明書を有効化すればまたアクセス可能となります。

※例
センシングデータ → AWS IoT → DynamoDB
画像データ → AWS IoT → S3（ちなみに128KB）
イベント発生 → AWS IoT → Lambda実行

## 使用するもの

-  AWS
    -  AWS IoT
    -  [AWS IoT Device SDK for Python](https://s3.amazonaws.com/aws-iot-device-sdk-python-docs/sphinx/html/index.html#)
    -  S3
-  Raspberry
-  ホストPC
    -  Macを使用してます

## 前提条件

- AWS側
    -  [Raspberry Pi の接続](http://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-sdk-setup.html)が完了していること
-  Raspberry Pi側
    -  OSインストールが完了し、ネットワーク、SSHの接続ができること

# AWS 側の準備

準備を行います

-  S3に保存先のバケットを作成
-  AWS IoTでルールを作成

## AWS IoTでルール、S3バケットを作成

AWS コンソールから「AWS IoT」をひらく
「ACT」をクリック
<img width="512" alt="AWS_IoT_1.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/2b617c34-34cc-0102-5a1f-0de16ce0bf74.png">

「作成」をクリック
<img width="512" alt="AWS_IoT_2.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/7ede2b5b-5e17-d19f-9fa0-62db2e677510.png">

「名前」「説明」を入力
<img width="512" alt="AWS_IoT_3.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/cc01ebca-90ce-0599-52bc-b12c16f50ded.png">

「属性：*」「トピックフィルター：sdk/test/Python」を入力
※トピックは送信時に設定するtopicです
<img width="512" alt="AWS_IoT_4.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/5bad8643-f710-09ed-8b3f-82406244172a.png">

「アクションの追加」でS3アクションを追加
<img width="512" alt="AWS_IoT_5.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/f295287c-8d96-a4f7-7da8-18ed95a0ae07.png">

「S3」を設定して「アクションの設定」をクリック
<img width="512" alt="AWS_IoT_6.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/a84ccaec-c544-74dc-56b3-d4c797b4aefd.png">

S3アクションの詳細は[こちら](http://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/s3-rule.html)を参照
「新しいリソースを作成する」でS3バケットを追加
<img width="512" alt="AWS_IoT_7.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/96e55214-4a0c-170b-9665-c83b79c19ac9.png">

「バケットを作成する」をクリック
<img width="512" alt="S3_Management_Console.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/8c7b64b9-2137-bf97-2337-1bec7fafcc76.png">

「バケット名」を入力して「次へ」
<img width="512" alt="S3_Management_Console_1.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/72ba5f52-deba-def5-5902-19c3cefbed56.png">

「次へ」
<img width="512" alt="S3_Management_Console_2.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/60ad1550-fc2a-5485-43f1-0e27f4442d9d.png">

「次へ」
<img width="512" alt="S3_Management_Console_3.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/da193aa2-5982-4107-9ddc-a9823de894c9.png">

「バケットを作成」
<img width="512" alt="S3_Management_Console_4.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/acc141c4-cced-98a0-f3b8-d6d54254cd38.png">

<img width="512" alt="S3_Management_Console_5.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/46e048d8-bad8-1381-0fce-0a03bf4f7b55.png">

「S3バケット」横の更新ボタンをクリックして、作成したS3バケットを選択
「新しいロールの作成」も行う
<img width="512" alt="AWS_IoT_9.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/716f9492-5adf-6805-875e-443855ac0a47.png">

「IAMロール名」入力して「新しいロールの作成」をクリック
<img width="512" alt="AWS_IoT_10.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/a6ecac81-a0a1-5732-54ff-949f58cc3ba4.png">

「キー：\${topic()}/${timestamp()}」を入力して「アクションの追加」をクリック
※キーは[こちら](http://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/s3-rule.html)を参照
※【追記】キーを「\${topic()}/${timestamp()}.png」にすればS3で画像データとして認識されます
※画像は「更新」になっている理由：新規登録後、キーをかえるために更新を行ったためです
<img width="512" alt="AWS_IoT_14.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/d0b30790-0159-bd67-5cfa-4e75794b6099.png">

「ルールを作成する」をクリックで完成です！
<img width="512" alt="AWS_IoT_13.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/bab6dfcc-244a-7794-463c-927a3ee93c3b.png">

# Raspberry Pi 側の準備

## 必要なライブラリ等をインストール

- 仮想環境を作成

```
# テスト用のディレクトリ作成
pi@chinopi:~ $ mkdir aws_iot_test
pi@chinopi:~ $ 
pi@chinopi:~ $ cd aws_iot_test/
pi@chinopi:~/aws_iot_test $ 
# 仮想環境
pi@chinopi:~/aws_iot_test $ sudo apt-get install python3-venv
Reading package lists... Done
Building dependency tree       
Reading state information... Done
・
・
・
pi@chinopi:~/aws_iot_test $ 
# テスト用の仮想環境作成
pi@chinopi:~/aws_iot_test $ python3 -m venv env
pi@chinopi:~/aws_iot_test $ 
# 仮想環境のactivate
pi@chinopi:~/aws_iot_test $ source env/bin/activate
(env) pi@chinopi:~/aws_iot_test $ 
(env) pi@chinopi:~/aws_iot_test $ python -V
Python 3.4.2
(env) pi@chinopi:~/aws_iot_test $
```

- SSLのバージョン確認

 [AWS IoT Device SDK for Python](https://github.com/aws/aws-iot-device-sdk-python/blob/master/README.rst)
README の「Installation - Minimum Requirements」を参照

```
# SSLのバージョン確認
(env) pi@chinopi:~/aws_iot_test $ python 
Python 3.4.2 (default, Oct 19 2014, 13:31:11) 
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ssl
>>> ssl.OPENSSL_VERSION
'OpenSSL 1.0.1t  3 May 2016'
>>> 
>>> exit()
(env) pi@chinopi:~/aws_iot_test $
```
- [AWS IoT Device SDK for Python](https://github.com/aws/aws-iot-device-sdk-python/blob/master/README.rst)をインストール

```
(env) pi@chinopi:~/aws_iot_test $ pip install AWSIoTPythonSDK
Downloading/unpacking AWSIoTPythonSDK
  Downloading AWSIoTPythonSDK-1.2.0.tar.gz (67kB): 67kB downloaded
  Running setup.py (path:/tmp/pip-build-n3u557w8/AWSIoTPythonSDK/setup.py) egg_info for package AWSIoTPythonSDK
    
Installing collected packages: AWSIoTPythonSDK
  Running setup.py install for AWSIoTPythonSDK
    
Successfully installed AWSIoTPythonSDK
Cleaning up...
(env) pi@chinopi:~/aws_iot_test $ 
```

- サンプルプログラムのclone、証明書等の配置ディレクトリの作成

```
(env) pi@chinopi:~/aws_iot_test $ git clone https://github.com/aws/aws-iot-device-sdk-python.git
Cloning into 'aws-iot-device-sdk-python'...
remote: Counting objects: 165, done.
remote: Total 165 (delta 0), reused 0 (delta 0), pack-reused 165
Receiving objects: 100% (165/165), 123.27 KiB | 170.00 KiB/s, done.
Resolving deltas: 100% (61/61), done.
Checking connectivity... done.
(env) pi@chinopi:~/aws_iot_test $ 
(env) pi@chinopi:~/aws_iot_test $
(env) pi@chinopi:~/aws_iot_test $ cd aws-iot-device-sdk-python/
(env) pi@chinopi:~/aws_iot_test/aws-iot-device-sdk-python $ ls
AWSIoTPythonSDK  CHANGELOG.rst  LICENSE.txt  MANIFEST.in  NOTICE.txt  README.rst  samples  setup.cfg  setup.py
(env) pi@chinopi:~/aws_iot_test/aws-iot-device-sdk-python $ 
# 証明書配置用のディレクトリ
(env) pi@chinopi:~/aws_iot_test/aws-iot-device-sdk-python $ mkdir certs
(env) pi@chinopi:~/aws_iot_test/aws-iot-device-sdk-python $ 
```

## 証明書等をRaspberry Pi に配置

- SCPで証明書を使用して転送

```
# ホストPCで実行（プログラム実行時に使用）
$ ls
XXXXXXXXXX-certificate.pem.crt XXXXXXXXXX-private.pem.key     XXXXXXXXXX-public.pem.key      root-CA.crt
$ 
# 証明書
$ scp ./XXXXXXXXXX-certificate.pem.crt pi@chinopi.local:/home/pi/aws_iot_test/aws-iot-device-sdk-python/certs
・・・
$ 
# プライベートキー
$ scp ./XXXXXXXXXX-private.pem.key pi@chinopi.local:/home/pi/aws_iot_test/aws-iot-device-sdk-python/certs
・・・
$ 
# ルートCA証明書
$ scp ./root-CA.crt pi@chinopi.local:/home/pi/aws_iot_test/aws-iot-device-sdk-python/certs
・・・
$
```
- テスト用の画像も転送（プログラム実行時に使用）

```
# ホストPCで実行
$ ls -la test.png
-rw-r--r--@ 1 chinoyuuji  staff  37362 11  7 18:25 test.png
$ 
$ scp ./test.png /home/pi/aws_iot_test/aws-iot-device-sdk-python/samples/basicPubSub
・・・
$ 
```

# プログラム準備、実行

## 画像をbase64に変更

```
(env) pi@chinopi:~ $ cd aws_iot_test/aws-iot-device-sdk-python/samples/basicPubSub/ $
(env) pi@chinopi:~/aws_iot_test/aws-iot-device-sdk-python/samples/basicPubSub $ cp basicPubSub.py basicPubSub_base64.py
(env) pi@chinopi:~/aws_iot_test/aws-iot-device-sdk-python/samples/basicPubSub $ ls -la
total 72
drwxr-xr-x 2 pi pi  4096 Nov 10 16:12 .
drwxr-xr-x 6 pi pi  4096 Nov 10 13:28 ..
-rw-r--r-- 1 pi pi  4414 Nov 10 13:28 basicPubSubAsync.py
-rwxr-xr-x 1 pi pi  3834 Nov 10 13:28 basicPubSub_CognitoSTS.py
-rwxr-xr-x 1 pi pi  4149 Nov 10 16:12 basicPubSub_base64.py
-rwxr-xr-x 1 pi pi  3874 Nov 10 13:28 basicPubSub.py
-rw-r--r-- 1 pi pi 37362 Nov 10 15:17 test.png
(env) pi@chinopi:~/aws_iot_test/aws-iot-device-sdk-python/samples/basicPubSub $ cb basicPubSub.py basicPubSub_copy.py
(env) pi@chinopi:~/aws_iot_test/aws-iot-device-sdk-python/samples/basicPubSub $ vi basicPubSub_copy.py 
```

「★★・・・★★」部分を変更、追加
※【追加】bytearrayの部分をバイナリデータとすればそのままバイナリデータを送信できます

```python:basicPubSub_base64.py

〜〜〜〜〜〜〜〜〜

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import logging
import time
import argparse
# ★★追加★★
import base64

〜〜〜〜〜〜〜〜〜

time.sleep(2)

# Publish to the same topic in a loop forever
# ★★コメントにする★★
# loopCount = 0
# while True:
#     myAWSIoTMQTTClient.publish(topic, "New Message " + str(loopCount), 1)
#     loopCount += 1
#     time.sleep(1)
# ★★追加★★
binary = open('test.png', 'rb').read()
binary_base64 = base64.b64encode(binary)
myAWSIoTMQTTClient.publish(topic, bytearray(binary_base64), 1)
```

※ base64エンコードしたデータを送信しようとした時に以下エラーが発生したため、「bytearray」に変換しています

```
TypeError: payload must be a string, bytearray, int, float or None.
```

## プログラム実行

実行形式はこちら

```
python basicPubSub_base64.py -e [エンドポイント] -r [ルートCA証明書] -c [証明書] -k [プライベートキー]
```
※ エンドポイントはこちらを参照：[Raspberry Pi の接続](http://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-sdk-setup.html)

プログラム実行

```
(env) pi@chinopi:~/aws_iot_test/aws-iot-device-sdk-python/samples/basicPubSub $
(env) pi@chinopi:~/aws_iot_test/aws-iot-device-sdk-python/samples/basicPubSub $ python basicPubSub_base64.py -e XXXXXXXXXXXXXX.iot.ap-northeast-1.amazonaws.com -r ~/aws_iot_test/aws-iot-device-sdk-python/certs/root-CA.crt -c ~/aws_iot_test/aws-iot-device-sdk-python/certs/XXXXXXXXXX-certificate.pem.crt -k ~/aws_iot_test/aws-iot-device-sdk-python/certs/XXXXXXXXXX-private.pem.key
2017-11-11 12:33:45,590 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Initializing MQTT layer...
2017-11-11 12:33:45,609 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Registering internal event callbacks to MQTT layer...
・
・
・
2017-11-11 12:33:48,378 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - This custom event callback is for pub/sub/unsub, removing it after invocation...
(env) pi@chinopi:~/aws_iot_test/aws-iot-device-sdk-python/samples/basicPubSub $
```

# S3に保存されたデータを確認

## S3 からダウンロード

AWS コンソールのS3をひらく
作成したS3バケット名をクリック
<img width="512" alt="S3_Management_Console_6.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/2a5f39e4-6faa-3605-ab9e-c4cd57d1916b.png">

フォルダ名をクリックしていく
<img width="512" alt="S3_Management_Console_7.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/de6705ab-be7b-8064-bfba-cfb786db57fa.png">

<img width="512" alt="S3_Management_Console_8.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/f9149a27-933d-c4e2-310b-736d7b3ab2f6.png">

<img width="512" alt="S3_Management_Console_9.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/7ab0688e-f9bd-0b1a-1bca-a9b0ef781418.png">

<img width="512" alt="S3_Management_Console_10.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/b4110779-6067-736a-9050-855314b900bd.png">

ファイルのチェックボックスをチェックして、ダウンロード
<img width="512" alt="S3_Management_Console_12.png" src="https://qiita-image-store.s3.amazonaws.com/0/52947/5fc29fe1-d709-4314-6fdb-767b141bb718.png">

## ホストPCでデコードして画像をみてみる

```
$ 
$ ls -la 1510409639497
-rw-r--r--@ 1 chinoyuuji  staff  49816 11 11 23:16 1510409639497
$ python3
Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 
>>> import base64
>>> 
>>> text = open('1510409639497', 'rt').read()
>>> 
>>> binary = base64.b64decode(text)
>>> 
>>> f = open('test_1.png', 'wb')
>>> f.write(binary)
37362
>>> f.close()
>>> 
```

`test_1.png` をひらいて画像が表示されればOK！