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


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

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

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

前提など

制約事項

参考の方にも記載がありますが、AWS IoTとの1回の送信は 最大128KBの制約 ありますので、注意してください!
AWS IoTの制限

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

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側
  • Raspberry Pi側
    • OSインストールが完了し、ネットワーク、SSHの接続ができること

AWS 側の準備

準備を行います

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

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

AWS コンソールから「AWS IoT」をひらく
「ACT」をクリック

「作成」をクリック

「名前」「説明」を入力

「属性:*」「トピックフィルター:sdk/test/Python」を入力
※トピックは送信時に設定するtopicです

「アクションの追加」でS3アクションを追加

「S3」を設定して「アクションの設定」をクリック

S3アクションの詳細はこちらを参照
「新しいリソースを作成する」でS3バケットを追加

「バケットを作成する」をクリック

「バケット名」を入力して「次へ」

「次へ」

「次へ」

「バケットを作成」

「S3バケット」横の更新ボタンをクリックして、作成したS3バケットを選択
「新しいロールの作成」も行う

「IAMロール名」入力して「新しいロールの作成」をクリック

「キー:\${topic()}/${timestamp()}」を入力して「アクションの追加」をクリック
※キーはこちらを参照
※【追記】キーを「\${topic()}/${timestamp()}.png」にすればS3で画像データとして認識されます
※画像は「更新」になっている理由:新規登録後、キーをかえるために更新を行ったためです

「ルールを作成する」をクリックで完成です!

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
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 $
(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 [email protected]:/home/pi/aws_iot_test/aws-iot-device-sdk-python/certs
・・・
$ 
# プライベートキー
$ scp ./XXXXXXXXXX-private.pem.key [email protected]:/home/pi/aws_iot_test/aws-iot-device-sdk-python/certs
・・・
$ 
# ルートCA証明書
$ scp ./root-CA.crt [email protected]:/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の部分をバイナリデータとすればそのままバイナリデータを送信できます

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 の接続

プログラム実行

(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バケット名をクリック

フォルダ名をクリックしていく

ファイルのチェックボックスをチェックして、ダウンロード

ホスト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!