Atom's tech blog

AWS IoT Device Shadowを使ってみた!(Part4)

f:id:iAtom:20201218174417j:plain


本記事は、最終目標である遠隔からDevice Shadow機能を使ってIoT Device制御をまとめたものです。 なお、4つの記事の中で今回は 下記の「 Part4 」です

主な全体記事の流れです。

■ Part1 : API Gatewayから起動するLambda関数の作成

■ Part2:API GatewayAPI作成

■ Part3 : AWS IoT Deviceとshadow作成

■ Part4 : iPhoneアプリ作成 と 遠隔Camera起動確認

iPhoneアプリも初心者で......。

プロジェクトの作り方や、viewイメージで作成したボタンをソースコード接続や、ボタンをタップしたメソッド生成方法..... などは割愛します。

注意

2020/12/17からboto3でIoT CoreのDevice ShadowにアクセスするとSSLエラーが発生する事象が出ています。

AWSのcertifiライブラリが「2020.12.5」にバージョンアップされたことが原因らしく、下記サイトを参考に応急処置を行い、問題なく動作できることを確認しました。

その時のエラーメッセージです。

  {"errorMessage": "SSL validation failed for https://data.iot.ap-northeast-1.amazonaws.com/things/RaspberryPi_Camera/shadow [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate


応急処置方法のサイトをリンク貼り付けます。

certifiライブラリ「2020.11.8」版をMACで作成し、その「 layer.zip 」ファイルをAWS Lambdaコンソールから「 レイヤー追加 」、対象となるLambda関数に適用でいけました。


dev.classmethod.jp


正式にはIoT CoreのATSエンドポイント指定することで対応可能なようです。 すみません。自分は確認していません。ご了承ください。

dev.classmethod.jp

AWS構成図

赤点線で囲んだ箇所を今回作成します。


f:id:iAtom:20201217153955j:plain

Appプロジェクト環境

使用するinterfaceは「 Storyboard 」、言語(Language)は「 Swift 」を使用します。


f:id:iAtom:20201217131223j:plain

Alamofireインストール

Alamofireとは

AlamofireはiOSアプリでネットワーク通信(HTTP、REST APIなど)を簡単に使うことができるライブラリです。ネイティブのネットワーク通信のライブラリを使用するより簡単に実装ができるようです。

アプリ作成初心者にはおすすめらしいので「 Alamofire 」を使用します。

インストール

いろいろなサイトでインストール方法が掲載されていますが、一番簡単な方法でやります。

Xcodeの「 File 」 -> 「 Swift Packages 」 -> 「 Add Package Dependency.... 」を選択します。


f:id:iAtom:20201217131720j:plain


Alamofireのgitリポジトリアドレスを入力 「 https://github.com/Alamofire/Alamofire 」を入力し、「 Next 」ボタンをクリックします。


f:id:iAtom:20201217131850j:plain


Branch 」のラジオボタンで選択し、「 Next 」ボタンをクリックします。


f:id:iAtom:20201217132014j:plain


Alamofireのチェックボックスにチェックを入れて、「 Next 」ボタンをクリックします。


f:id:iAtom:20201217132151j:plain


Alamofireが表示されている状態を確認します。


f:id:iAtom:20201217132520j:plain


これでAlamofireがインストールできました。

アプリソースコード

冒頭でも説明しましたが、viewイメージで作成したボタンをソースコード接続や、ボタンをタップなどのメソッド生成方法の図解は割愛します。

Main.storyboardファイルにボタン、テキストをGUI上で配置しておいてください

ソースファイルのコピペだけではエラーとなりますので、ご注意ください。

ViewController.swift

単純な「ON」と「OFF」ボタンを配置し、タップ動作を入れているだけのシンプル? な初心者作成ソースコードです。

import UIKit
import Alamofire

class ViewController: UIViewController {

    @IBOutlet weak var buttonON: UIButton!
    @IBOutlet weak var buttonOff: UIButton!

    //  AWS API Gateywayで設定し生成されたCamera ONのPOST URL
    //  ■ xxxxxxxxxxxは各自の環境に合わせてください。
    var awsApiGatewayURLOn = "https://xxxxxxxxxx.execute-api.xxxxxxxxxxxxxxx.amazonaws.com/development/test?camera=1"
    //  AWS API Gateywayで設定し生成されたCamera OFF POST URL
    //  ■ xxxxxxxxxxxは各自の環境に合わせてください。
    var awsApiGatewayURLOff =
        "https://xxxxxxxxxxxxx.execute-api.ap-xxxxxxxxxxxx.amazonaws.com/development/test?camera=0"
    //  AWS API Gateywayで生成したAPIキー
    //  ■ xxxxxxxxxxxは各自の環境に合わせてください。
    var awsApiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

    override func viewDidLoad() {
        super.viewDidLoad()
        
    }

    override func viewDidAppear(_ animated: Bool) {
       //  ボタンONの形を変える
        buttonON.frame = CGRect(x:25,y:131,width:330,height:150)
        buttonON.layer.masksToBounds = false
        buttonON.layer.cornerRadius = 10.0
        // ボタンの影
        buttonON.layer.shadowOffset = CGSize(width: 3, height: 3)// 影の方向
        buttonON.layer.shadowOpacity = 0.4 // 影の濃さ
        buttonON.layer.shadowRadius = 7 // 影のぼかし量

        //  ボタンOFFの形を変える
        buttonOff.frame = CGRect(x:25,y:300,width:330,height:150)
        buttonOff.layer.masksToBounds = false
        buttonOff.layer.cornerRadius = 10.0
        
        // ボタンの影
        buttonOff.layer.shadowOffset = CGSize(width: 3, height: 3)
        buttonOff.layer.shadowOpacity = 0.4
        buttonOff.layer.shadowRadius = 7
    }
    
    // ONボタンを押した状態の時に起動
    @IBAction func buttonON_touchdown(_ sender: UIButton) {
        
        // 色をsystemGray3に変える
        buttonON.backgroundColor = UIColor.systemGray3
    }
    
    // OFFボタンを押して離した時に起動
    @IBAction func buttonON_touchUp(_ sender: UIButton) {
        // 色をsystemYellowに変える
        buttonON.backgroundColor = UIColor.systemYellow
        
        // AWS Gatewayアクセス CameraON URL
        AF.request(awsApiGatewayURLOn,method: .post,headers: ["X-API-KEY":awsApiKey]).response { response in debugPrint(response)}
    }
    
    // OFFボタンを押した状態の時に起動
    @IBAction func buttonOFF_touchdown(_ sender: UIButton) {
        // 色をsystemGray3に変える
        buttonOff.backgroundColor = UIColor.systemGray3

    }
    
    // OFFボタンを押して離した時に起動
    @IBAction func buttonOFF_touchUp(_ sender: UIButton) {
        // 色をsystemTealに変える
        buttonOff.backgroundColor = UIColor.systemTeal
        
        // AWS Gatewayアクセス CameraOFF URL
        AF.request(awsApiGatewayURLOff,method: .post,headers: ["X-API-KEY":awsApiKey]).response { response in debugPrint(response)}
        
    }
}

App起動

Xcodeでビルドし、起動するとON/OFFの2つのボタンを表示します。

下記イメージはMAC上で表示したSimulatorです。

実際に動作させるときは、iphoneにAppを転送しています。


f:id:iAtom:20201217135734j:plain


AWS設定やIoT Deviceソースコード、Lambda関数ソースコードiOS Appソースコードの説明は以上です。

これでiPhoneからIoT Deviceに接続しているカメラを起動する準備ができました。

それでは実際に試してみます。

App → IoT Core Shadow 動作

Appから「 ON 」ボタンを押してIoT CoreのShadowテーブルが更新することを確認します。 イメージは赤点線で囲んだ箇所のルートです。


f:id:iAtom:20201217140536j:plain

IoT CoreのShadowの状態確認

IoT Coreコンソールにアクセスします。

コンソール画面の「 管理 」-> 「 モノ」-> 「 RaspberryPi_Camera 」-> 「 シャドウ 」-> 「 classic shadow 」をクリックします。

"camera" : "0"はカメラ停止 "camera" : "1"はカメラ起動 です。


f:id:iAtom:20201218111035j:plain

Shadow状態確認

iOS Appから「 ON 」ボタンをクリックします。

Appから送信された「 camera=1 」のREST APIが、最終的にIoT Core Shadowに到達し、「 desired 」の「 "camera" : "1" 」に変化したことが確認できます。


f:id:iAtom:20201218111803j:plain


更に「 desired 」と「 reported 」のcamera変数の値が異なることをAWS shadowが検出し、「 delta 」で値をステータスとして表示しています。

このステータスの確認は、一回ブラウザの更新ボタンで画面更新で表示されます。


f:id:iAtom:20201218112946j:plain


次にiOS Appからの指示とIoT Deviceの連携を一連の動作で見たいので、一旦OFFボタンをタップして、「 desired 」の「 "camera" : "0" 」にします。

REST API制御

iOS Appを使用しなくても、直接REST APIを呼び出して制御することもできます。

x-api-key、httpsアドレスの 「xxxxxxxxxxxxxxxxxxxx」は各自の環境に合わせてください。

また「ap-northeast-1」.は、API Gatewayで作成したリージョン(今回は東京)です。

ON/OFFの違いは「 camera=0 」と 「 camera=1 」のみです。

Camera ONのAPI
   curl -X POST --header "x-api-key:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
   https://xxxxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/"test?camera=0"
Camera OFFのAPI
   curl -X POST --header "x-api-key:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
   https://xxxxxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev/"test?camera=1"

IoT Device shadow起動とiOS App制御

IoT DeviceがAWS IoT CoreにShadowにアクセスし、「 desired 」の「 "camera" : "1" 」に変化したことを検出し、「reported 」の「 "camera" : "1" 」に変化させ、更にIoT Deviceのカメラが起動していることを確認します。

赤点線部分の確認です。


f:id:iAtom:20201218113949j:plain

Pythonスクリプト実行

IoT Device(RaspberryPi)にアクセスし、shadow起動のPythonスクリプトを起動します。

    $ python3 shadow_camera_test.py


起動後、標準出力で表示ログを出力します。

下記の表示イメージは、全て「AWSIoTPythonSDK.MQTTLib」ライブラリで出力しています。

  2020-12-17 10:01:55,642 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Initializing MQTT layer...
  2020-12-17 10:01:55,644 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Registering internal event callbacks to MQTT layer...
  2020-12-17 10:01:55,644 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - MqttCore initialized
  2020-12-17 10:01:55,645 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Client id: RaspberryPi
  2020-12-17 10:01:55,645 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Protocol version: MQTTv3.1.1
  2020-12-17 10:01:55,645 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Authentication type: TLSv1.2 certificate based Mutual Auth.
  2020-12-17 10:01:55,645 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Configuring offline requests queueing: max queue size: 0
  2020-12-17 10:01:55,646 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Configuring offline requests queue draining interval: 0.100000 sec
  2020-12-17 10:01:55,646 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Configuring endpoint...
  2020-12-17 10:01:55,647 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Configuring certificates...
  2020-12-17 10:01:55,647 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Configuring reconnect back off timing...
  2020-12-17 10:01:55,648 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Base quiet time: 1.000000 sec
  2020-12-17 10:01:55,648 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Max quiet time: 32.000000 sec
  2020-12-17 10:01:55,648 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Stable connection time: 20.000000 sec
  2020-12-17 10:01:55,649 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Configuring connect/disconnect time out: 10.000000 sec
  2020-12-17 10:01:55,649 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Configuring MQTT operation time out: 5.000000 sec
  2020-12-17 10:01:55,649 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing sync connect...
  2020-12-17 10:01:55,649 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing async connect...
  2020-12-17 10:01:55,649 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Keep-alive: 600.000000 sec
  2020-12-17 10:01:55,650 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Event consuming thread started
  2020-12-17 10:01:55,651 - AWSIoTPythonSDK.core.protocol.mqtt_core - DEBUG - Passing in general notification callbacks to internal client...
  2020-12-17 10:01:55,651 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Filling in fixed event callbacks: CONNACK,DISCONNECT,MESSAGE
  2020-12-17 10:01:55,774 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Starting network I/O thread...
  2020-12-17 10:01:55,848 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Produced [connack] event
  2020-12-17 10:01:55,849 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Dispatching [connack] event
  2020-12-17 10:01:55,850 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - No need for recovery
  2020-12-17 10:01:55,851 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Invoking custom event callback...
  2020-12-17 10:01:55,852 - AWSIoTPythonSDK.core.protocol.mqtt_core - INFO - Performing sync subscribe...
  2020-12-17 10:01:55,852 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Adding a new subscription record:$aws/things/RaspberryPi_Camera/shadow/update/delta qos: 0
  2020-12-17 10:01:55,853 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Filling in custom suback event callback...
  2020-12-17 10:01:55,907 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Produced [suback] event
  2020-12-17 10:01:55,908 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Dispatching [suback] event
  2020-12-17 10:01:55,908 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - Invoking custom event callback...
  2020-12-17 10:01:55,909 - AWSIoTPythonSDK.core.protocol.internal.clients - DEBUG - This custom event callback is for pub/sub/unsub, removing itafter invocation...
  2020-12-17 10:01:57,912 - AWSIoTPythonSDK.core.shadow.deviceShadow - INFO - Subscribed to delta topic for deviceShadow:RaspberryPi_Camera
  2020-12-17 10:02:01,042 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Produced [message] event
  2020-12-17 10:02:01,042 - AWSIoTPythonSDK.core.protocol.internal.workers - DEBUG - Dispatching [message] eventdelta/RaspberryPi_Camera


ASW IoT Coreコンソールからshadow状態の確認します。

まだカメラ停止状態です。


f:id:iAtom:20201218153144j:plain


iOS Appから「 ON 」ボタンをタップすると、「 desired 」の「 "camera" : "1" 」に変化したことを検出し、「reported 」の「 "camera" : "1" 」になりました。


f:id:iAtom:20201218163423j:plain


Iot Device(RaspberryPi)のshadow変化検出ログイメージです。


  ++++++++DELTA++++++++++
  camera: 1
  version: 12
  +++++++++++++++++++++++

  updating...


最後にカメラが起動した時の画像です。

時間がIoT Core コンソールのShadowステータスと異なりますが、ご了承ください。


f:id:iAtom:20201218163955j:plain

最後に

途中AWSアップデートで動作しない時もありましたが、目標としているAWS Iot Device Shadow機能を使って遠隔操作を確認することができました。

この方法は見守りカメラなどでも使用されているようですね。ご参考にしていただければと思います。