ITブログ   HTML5.asia

ClovaのExtensionサーバーをLambdaで構築する方法

1. Clova Extensions Kit SDKについて

CEKが、会話の解析をして、JSON形式のデータを作成しています。そのデータを、少ない労力で処理できるように、Clova Extensions Kitソフトウェア開発キット(CEK SDK)が、用意されています。 もちろん、単なる、JSON形式のメッセージのやり取りに過ぎないので、自前で実装することも可能です。 実際にやり取りされるメッセージについては、「ClovaとIoT家電 Clova ⇒ Extensionサーバー」を参照して下さい。
提供される言語は、Node.js、Python、Java、Swift、Kotlin、Elixir、Goです。ここではPythonを使用します。下記手順により開発を進めていきます。

2. Python3.7.2環境作成

          ## Python 3.7.2のコンパイル・ビルド・インストール
          cd ~
          mkdir tmp
          cd tmp
          wget https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tgz
          tar zxvf Python-3.7.2.tgz 
          cd Python-3.7.2 
          ./configure --prefix=/usr/bin/python3.7.2
          make
          make install
          
          ## virtualenvを用いてPython 3.7.2仮想環境を作成し、バージョンを3.7.2に切りかえる
          cd ~
          virtualenv my_env_python --python=/usr/bin/python3.7.2/bin/python3.7
          cd my_env_python
          source bin/activate
          python -V
          
          ## CEK SDKをgit cloneしてテスト実行
          git clone https://github.com/line/clova-cek-sdk-python.git
          cd  clova-cek-sdk-python
          python -m unittest discover -s ./test -p 'test_*.py'
        

Pythonの実行環境依存のライブラリィも必要なため、Amazon Linuxで構築したEC2インスタンス上で実行しています。EC2の他にAmazon Linuxのdockerイメージを利用する方法もあります。 Amazon Linux上で行う理由は、Lambdaの実行環境のOSが、Amazon Linuxのためです。 詳細は、AWS Lambda Runtimesを参照して下さい。
8~10行目で、ソースからPython3.7.2をコンパイルしてビルドしています。14行目でvirtualenvを使用して、既存の環境を壊さずにPython 3.7.2の仮想環境を作成しました。virtualenvの使用は必須ではありません。 また3.7.2のバージョンを使用しているのは、Lambda(ランタイム Python 3.7)で使用しているバージョンは、3.7.2 (default, Mar 1 2019, 11:28:42)※1で、それに合わせたためです。
22行目でテスト実行しています(初回時に必要なライブラリィがイントールされる。インストールされたライブラリィは、Lambdaで実行するときに必要になります)。 テストの成功時には、「Ran 53 tests in 0.008s OK」※2と表示されます。
※1 print(sys.version)の実行結果で確認しました。(確認日 2019年4月)
※2 clova-cek-sdk-pythonのバージョンが1.1.1の場合

3. ライブラリィ収集

          mkdir -p python_layer/python/lib/python3.7/site-packages
          cd python_layer/python/lib/python3.7/site-packages 

          ## 必要なpythonのソース及びコンパイルされた環境依存のライブラリィをコピーします
          cp -r /home/ec2-user/my_env_python/lib/python3.7/site-packages/asn1crypto .
          cp -r /home/ec2-user/my_env_python/lib/python3.7/site-packages/cffi .
          cp -r /home/ec2-user/my_env_python/lib/python3.7/site-packages/cryptography .
          cp -r /home/ec2-user/my_env_python/lib/python3.7/site-packages/idna .
          cp -r /home/ec2-user/my_env_python/lib/python3.7/site-packages/pycparser .
          cp -r /home/ec2-user/my_env_python/lib/python3.7/site-packages/.libs_cffi_backend .
          cp -r /home/ec2-user/my_env_python/lib/python3.7/site-packages/six.py .
          cp -r /home/ec2-user/my_env_python/lib/python3.7/site-packages/easy_install.py .
          cp -r /home/ec2-user/my_env_python/lib/python3.7/site-packages/_cffi_backend.cpython-37m-x86_64-linux-gnu.so .
          cp -r /home/ec2-user/my_env_python/clova-cek-sdk-python/cek .

          ## 不要なpythonキャッシュの削除
          find ./ -type d -name "__pycache__" -prune -exec rm -r {} \;

          ## ライブラリィをzip形式に圧縮  (Lambda Lyaerを使用しない場合)
          zip -r site-packages.zip .

          ## ライブラリィをzip形式に圧縮  (Lambda Lyaerを使用する場合)
          ## python_layerに移動
          cd ../../../..
          zip -r layer.zip python/

        

Lambdaで提供されていないライブラリィは、自分でアップロードする必要があります。Pythonのライブラリィには、コンパイルされた環境依存のネイティブライブラリィが 必要な場合もあります。 13行目がOS依存となるネイティブライブラリィとなります。その他のフォルダ配下にもネイティブライブラリィがあります。AWS IoT coreにアクセスするライブラリィが、TLS1.2以上を使用するため、Lambdaのランタイムは、Python 3.7を使用しています。 3.6の場合エラーになります。(バージョンによりOpenSSLのバージョンが違い、そのため対応しているTLSのバージョンに相違があます。)
17行目で、不要なPythonのキャッシュを削除をしているのは、アップロードするサイズが一定サイズ以上になると、LambdaのGUI上でソースが修正できなくなるため削除しています。
20行目では、Lamada Layerを使用しない場合の、zip圧縮方法です。使用する場合は、20行目を実行しないで、24行目以降を実行して下さい。 Lamada Layerは、2018年11月29日に発表され機能で、共通ライブラリィを登録でき、共通ライブラリィのバージョン管理を行うことができます。 注意事項として、ここで記載しているSKDを実行するのに必要なライブラリィは、clova-cek-sdk-pythonのバージョンが1.1.1の場合であり、将来、変わる可能性があります。

4. ハンドラー定義

下記インテントに対するハンドラーを定義して、LED照明を操作しています。
家電の操作は、AWS IoT Topic経由で、ラズベリーパイにメッセージを送付しています。そのあとに、赤外線を照射して家電を操作しています。

  1. LEDColorManagement
     照明の色を変える
  2. LEDOnManagement
     照明を消す
  3. LEDOffManagement
     照明をつける
  4. LEDIlluminanceUpManagement
     照明を明るくする
  5. LEDIlluminanceDownManagement
     照明を暗くする
   import boto3
   import cek
   import json
   from cek import Clova
   from cek.core import ApplicationIdMismatch
   from cryptography.exceptions import InvalidSignature

   def lambda_handler(event, context):
     speech_builder = cek.SpeechBuilder()
     response_builder = cek.ResponseBuilder()
     clova_handler = cek.RequestHandler("xxx.xxx.xxx")
     reprompt_message = "マスター、他に何かご用件はございますか。"

     @clova_handler.launch
     def launch_request_handler(clova_request):
       return response_builder.simple_speech_text("IoT管理を行います!")
     
     @clova_handler.intent("LEDColorManagement")
     def color_handler(clova_request):
       # スロット取得
       if clova_request.slot_value("led_color") != None:
         led_color = clova_request.slot_value("led_color")
         responseText = "照明を" + led_color + "色にしました。"
         publishTopic("light", "color", color=led_color)
       else:
         responseText = "使用できる色は、青、赤です。"
       return response_builder.simple_speech_text(responseText)

     @clova_handler.intent("LEDOnManagement")
     def turn_on_handler(clova_request):
       responseText = "照明をつけました。"
       publishTopic("light", "power", power="on")
       return response_builder.simple_speech_text(message=responseText)

     @clova_handler.intent("LEDOffManagement")
     def turn_off_handler(clova_request):
       responseText = "照明を消しました。"
       publishTopic("light", "power", power="off")
       return response_builder.simple_speech_text(message=responseText)

     @clova_handler.intent("LEDIlluminanceUpManagement")
     def illuminance_up_handler(clova_request):
       responseText = "照明を明るくしました。"
       publishTopic("light", "power", power="up")
       return response_builder.simple_speech_text(message=responseText)

     @clova_handler.intent("LEDIlluminanceDownManagement")
     def illuminance_down_handler(clova_request):
       responseText = "照明を暗くしました。"
       publishTopic("light", "power", power="down")
       return response_builder.simple_speech_text(message=responseText)

     @clova_handler.default
     def default_handler(clova_request):
       return response_builder.simple_speech_text("指示を正しく認識できませんでした。")

     @clova_handler.end
     def end_handler(clova_request):
       return response_builder.simple_speech_text("IoT管理を終了します。")

     try:
       response = clova_handler.route_request(request_body=event['body'].encode("UTF-8"),\
             request_header_dict=event['headers'])
       plain_text = speech_builder.plain_text(reprompt_message)
       response.reprompt = speech_builder.simple_speech(plain_text)
       return get_lambda_response(200, response)
     except InvalidSignature:
       return get_lambda_response(401, {"error": "InvalidSignature"})
     except ApplicationIdMismatch as e:
       return get_lambda_response(401, {"error": "WrongApplicationId"})
     except Exception as e:
       return get_lambda_response(500)

   def get_lambda_response(status_code, response={}):
     return {
       "statusCode": status_code,
       "headers": {"Content-Type": "application/json"},
       "body": json.dumps(response),
       "isBase64Encoded": False
     }

   def publishTopic(device_type, action, power=None, color=None):
     if device_type == "light":
       if action == "power":
         payload = '{"data": {"device_type": "' + device_type + '", "action": "' + action + '", "power": "' + \
              power + '"}}'
       elif action == "color":
         payload = '{"data": {"device_type": "' + device_type + '", "action": "' + action + '", "color": "' + \
              color + '"}}'
       else:
         return;
     else:
       return;
     iot = boto3.client("iot-data")
     iot.publish(topic="iot/1", qos=0, payload=payload)
        

公式SDKと、Python標準機能のデコレータ機能により、ハンドラーの実装は難しくありません。見てのとおり、Clovaとの会話のやり取りは非常に簡単です。 以下にそれぞれの処理について解説します。

5. セキュリティについて

CEKから呼び出される場合、HTTP Bodyの電子署名結果を、HTTPリクエストヘッダのパラメータに付与して呼ばれます。そのあとSDKの中で電子署名の検証を行います。検証エラーの場合は、 InvalidSignature例外が発生します。呼出元は、必ず電子署名の秘密鍵を保持しているCEKとなり、電子署名により改竄は困難になります。 また、電子署名の検証をすることでアクセス元の検証を行うことができます。 詳細は、公式ドキュメントの「リクエストメッセージを検証する」 を参照して下さい。 また、HTTPS通信を使用することで通信の内容は暗号化されます。

6. メッセージ仕様について

メッセージは、Node-Redで汎用的に処理できるようにしました。device_typeで操作対象を特定し、actionで操作内容を示しています。 ここでは、照明しか操作していないが、エアコン、テレビを操作する場合は、device_typeには、aricon、televisionが設定されます。

照明をつけて
          {
            "data": {
              "device_type": "light",
              "action": "power",
              "power": "on"
            }
          }
        
照明を消して
          {
            "data": {
              "device_type": "light",
              "action": "power",
              "power": "off"
            }
          }
        
照明を青にして
          {
            "data": {
              "device_type": "light",
              "action": "color",
              "color": "青"
            }
          }
        
照明を明るくして
          {
            "data": {
              "device_type": "light",
              "action": "power",
              "power": "up"
            }
          }
        

7. API Gateway定義

API GatewayのPOSTメソッドの定義する際に、必ず「Lambda プロキシ統合の使用」の使用にチェックして、定義して下さい。 定義したメソッドをデプロイして、Clova Developer Centerの「ExtensionサーバのURL」に、エンドポイントを設定します。

目次表示