前提

構成図

AWS Lambdaについて

今回のハンズオンでは、AWS Lambda(以下Lambda)を使ってLINE Botを作成します(初級者の方は運営が準備したBotを利用していただきます)。

AWS Lambdaについては以下の公式ページを参照してください。

https://aws.amazon.com/jp/lambda/

Lambdaにコードをデプロイする方法はいくつかありますが、今回は運営が準備したコードをAWSコンソールから貼り付けてデプロイします。

利用料金

Lambdaの利用料金は以下の公式ページをご覧ください。

https://aws.amazon.com/jp/lambda/pricing/

今回のハンズオンの内容は無料枠の中でご利用いただけます。翌月以降も費用が発生することはまずありませんが、不安な方はハンズオン終了後にリソースを削除してください。

Amazon API Gatewayについて

今回のハンズオンでは、LINE Botに対してインターネットからはAmazon API Gateway(以下API Gateway)を使ってアクセスします。

API Gatewayについて詳しくは以下の公式ページを参照ください。

https://aws.amazon.com/jp/api-gateway/

利用料金

API Gatewayの利用料金は以下の公式ページをご覧ください。

https://aws.amazon.com/jp/api-gateway/pricing/

今回のハンズオンの内容は無料枠の中でご利用いただけます。翌月以降も費用が発生することはまずありませんが、不安な方はハンズオン終了後にリソースを削除してください。

Amazon DynamoDBについて

今回のハンズオンでは、LINE Messaging APIの秘匿情報(チャネルシークレット/アクセストークン)をAmazon DynamoDB(以下DynamoDB)に保存し、チャネルIDをキーにしてLambdaから取得します。

DynamoDBならびにDynamoDBに秘匿情報を登録するためのページは運営が準備しています。

DynamoDBについては以下の公式ページをご覧ください。

https://aws.amazon.com/jp/dynamodb/

AWS IoT CoreならびにSORACOM Beamについて

今回のハンズオンでは、AWS IoT Core(以下IoT Core)ならびに「データ転送サービス SORACOM Beam」(以下、Beam)を用いてLINE Botからの命令をデバイスに届けます。

LINE BotがIoT CoreのMQTTトピックにメッセージを送信すると、Beam経由でMQTTトピックにサブスクライブしているデバイスにメッセージが届き、デバイスはそのメッセージに従って動作します。

今回はIoT CoreならびBeam、デバイスは運営で準備したものを利用します。

IoT Coreについて詳しくは以下の公式ページをご覧ください。

https://aws.amazon.com/jp/iot-core/

Beamについて詳しくは以下の公式ページをご覧ください。
https://soracom.jp/services/beam/

LINE Messaging APIについて

今回のハンズオンでは、LINE Messaging APIを使ってBotを作成し、LINEからデバイスに対して命令を送ります。

LINE Messaging APIについて詳しくは以下の公式ページを参照ください。

https://developers.line.biz/ja/services/messaging-api/

概要

LINEとシステムを連動させるには「LINE Messaging API」を利用します。LINE上では「Bot(ボット; 仮想的なユーザー)」として振舞い、メッセージの入力を受け付けてシステムへ転送したり、またシステムの処理結果をメッセージとしてLINEに表示します。

この作業ではBotの作成と、そのBotを動かすための認証情報の入手を行います。これは本ハンズオンだけでなく、今後も一般的に行う作業です。

また本ハンズオンでは、まず全員が運営で用意しているシステムを経由してデバイスを操作します。そのため入手した認証情報を、運営で用意しているシステムに保存してもらいます。(この作業は本ハンズオン限定の作業です)

作業

LINE Botのアカウント(LINE Messaging APIチャネル)を作成します。

以下の公式ドキュメントに従い、Messaging APIチャネルを作成してください。

https://developers.line.biz/ja/docs/messaging-api/getting-started/#using-console

LINE Developerコンソールで、作成したMessaging APIチャネルのチャネルID、チャネルシークレット、長期のチャネルアクセストークンを取得してメモしておいてください。

長期のチャネルアクセストークンについては以下の公式ドキュメントを参考にしてください。

https://developers.line.biz/ja/docs/messaging-api/channel-access-tokens/#long-lived-channel-access-tokens

チャネルシークレット等の登録

今回のハンズオンでは、LINE Messaging APIの秘匿情報(チャネルシークレット/チャネルアクセストークン)を運営が準備したDynamoDBに保存します。

DynamoDBへの登録は、運営が準備した以下のURLから行えます。先ほど保存したチャネルID、チャネルシークレット、チャネルアクセストークンを入力します。

https://www.appsheet.com/start/d8bc91a1-e3a5-4802-aaf5-d665ed747c4e

初回アクセス時には以下の画面が表示されますので、「ACCEPT」を押します。

チャネルID、チャネルシークレット、チャネルアクセストークンを入力し、「Save」を押します。

保存中は以下のように右上に赤丸の数字が表示されます。

以下のように表示されたら成功です。成功したらこのページは閉じることができます。

Webhook URLの設定

LINE Messaging APIチャネルに、Webhook URLを設定します。これによって、チャネルととLINE Bot(Lambdaで動いているプログラム)を接続します。まずは、運営が準備しているBotと接続してみます。

Messaging APIチャネルの「Messaging API設定」タブをクリックします。

当日運営から入手したURLを設定します。URL中の「{channel_id}」の部分はあなたのMessaging APIチャネルのチャネルIDを設定します。

例えば運営から入手したURLが「https://xxxxxxxxxxxx/{channel_id} 」で、あなたのチャネルIDが「123456」の場合、設定するURLは「https://xxxxxxxxxxxx/123456」です。

Webhook URLの設定方法は以下の公式ドキュメントを参考にしてください。セキュリティの設定は不要です。

https://developers.line.biz/ja/docs/messaging-api/building-bot/#setting-webhook-url

Webhookの動作を確認します。Webhook URLの設定の下にある「検証」ボタンを押し、下図のように「成功」と表示されることを確認します。


ヘルプメッセージをbotが返すように設定します。
Webhookの下の「Webhookの利用」をONに(スライダーを右に)します。

その下の「LINE公式アカウント設定」の「応答メッセージ」の「編集」を押します。

LINE Official Account Managerがブラウザの別ウインドウ(タブ)で開きます。

応答モードを「Bot」、応答メッセージを「オフ」、Webhookを「オン」に変えます。

トラブルシューティング

設定がうまく進まないときにご覧ください。ここまでの作業が出来ている場合は次に進んでください。

Webhookの動作確認のときにタイムアウトが起きる:Lambdaを立ち上げている最中にタイムアウトになっている可能性があります。再度「検証」ボタンを押してください。

実際に動作させてみましょう。

LINE Botと友達になる

LINE Developersコンソールの[Messaging API設定]タブにあるQRコードを読み取り、LINE Botと友達になります。

LINE Botに命令を送る

適当なメッセージをLINE Botに送る(トークルームで送信する)と、ヘルプメッセージが取得できます。

このメッセージに従い、「回転する角度(スペース)回転速度」というメッセージをBotに送ります。まずはサンプル通り「360 150」と入れてみます。ここまでの設定が正しく終わっていればzoomの画面に映っているデバイスが回転しますので、zoomの画面に注目してください。

概要

ここまでの手順では皆さんのLINE Messaging APIを運営が準備したBotに接続して動作させましたが、ここからはBotを皆さんの環境にデプロイしてみます。

構成図における「中級」の枠、AWS Lambdaの関数と、Amazon API Gateway によるWebhook受け付けです。

構築の順番はAWS Lambda → Amazon API Gateway です。構築が済んでいるシステムに近い近い方から構築すると、1つずつ検証できるので構築が容易です。

デプロイするコードのダウンロード

以下のURLから、運営が準備したコード一式をダウンロードします。

https://github.com/soracomug/line-bot-device-remote-control-handson/blob/main/files/handson.zip

「ダウンロード」ボタンを押してください。

ダウンロードしたhandson.zipファイルを展開してください。以下の2つのファイルが含まれていることを確認します。

AWS LambdaにBotのプログラムをデプロイします。

まず、AWSコンソールにアクセスします。右上のリージョンが「バージニア北部」になっていない場合は、プルダウンから変更してください。

検索窓に「lambda」と入れてLambdaのコンソールにアクセスします。

「関数の作成」を押し、新しい関数を作成します。

関数のタイプで「一から作成」を選び、関数名は「soracom-linedc-handson-20220128」と入力、ランタイムは「Python3.9」を選びます。それ以外の部分はデフォルトのままで「関数の作成」を押します。

コードソースに入っているデフォルトのコードを削除し、先ほどダウンロードしたファイル「linebot-for-handson-20220128.py」の内容をそのまま貼り付けます。

そして、「Deploy」ボタンを押してコードをデプロイします。

「設定」→「環境変数」→「編集」を押し、このLambda関数の環境変数を設定します。

「環境変数の追加」を6回押し、キーと値を入力する枠を6個作ります。そこに以下のキーと値を入れて、「保存」を押します。
・ DEST_AWS_ACCESS_KEY_ID → 当日運営にお問い合わせください

・ DEST_AWS_SECRET_ACCESS_KEY → 当日運営にお問い合わせください

・ DEST_ENDPOINT_URL → 当日運営にお問い合わせください

・ DEST_REGION_NAME → 当日運営にお問い合わせください

・ LINE_CHANNEL_SECRET → あなたのMessaging APIチャネルのチャネルシークレット

・ LINE_CHANNEL_ACCESS_TOKEN → あなたのMessaging APIチャネルのチャネルアクセストークン


保存したら、次にLambdaの実行時間(タイムアウト)を変更します。「一般設定」から「編集」を押します。

デフォルトではタイムアウトが3秒になっているので、これを30秒に変更して「保存」を押します。

保存できたら、動作テストを行います。

「テスト」を押し、テストイベントの名前に「test」と入力し、JSONが記載されている部分に以下のJSONを貼り付け、「変更を保存」を押します。

{
  "headers": {
    "x-line-signature": "LaGMWOqhEn84qmUAlwCx6OLOTe26Y4l0chpPItEKThM="
  },
  "pathParameters": {
    "channel_id": "9876543210"
  },
  "requestContext": {
    "stage": "test-invoke-stage"
  },
  "body": "{\"events\":[{\"replyToken\":\"DUMMY\",\"message\":{\"text\":\"90 120\"}}]}"
}

保存したら、「テスト」ボタンを押します。下図のように、「実行結果:成功」と出ていれば成功です。

トラブルシューティング

設定がうまく進まないときにご覧ください。ここまでの作業が出来ている場合は次に進んでください。

・コードのコピペが崩れてる、全角スペースが入ってるなどコードの問題: ファイルは文字コード UTF-8になっていますので、ファイルを開いたエディタによってはShiftJISになってしまいますと、文字化けします。エディタの設定で、UTF-8で開き直してください。

・成功したけど、想定通りの動作じゃない。: linebot-for-handson-20220128.pyの内容を貼り付けたあと、「Deploy」ボタンを押し忘れてますと、コードの変更が反映されず以下のような実行結果が表示されます。エディタ画面で「Deploy」ボタンを押してください。

・タイムアウトエラーが出る。: タイムアウト時間が初期設定の3秒のままだと、初回起動時にタイムアウトエラーになることがあります。タイムアウト時間の変更を行ってください。

AWS Lambdaにインターネットから接続できるように、Amazon API Gatewayを使ってHTTP REST APIのエンドポイントを作成します。

まず、AWSコンソールにアクセスし、検索窓に「api」と入れてAPI Gatewayのコンソールにアクセスします。

「APIを作成」を押します。

REST APIの「インポート」を押します。

「プロトコル」は「REST」を、「新しいAPIの作成」は「Swagger あるいは Open API 3 からインポート」を選択します。

「Swagger あるいは Open API 3 からインポート」のところに、先ほどダウンロードしたファイル「linebot-incoming-for-handson-20220128.json」の内容を貼り付けます。

「エンドポイントタイプ」は「リージョン」を選びます。

全ての入力・選択が終わったら「インポート」を押します。

下図のような画面に遷移すればインポート(作成)は成功です。

「/{channnen_id}」をクリックし、「今すぐ定義」をクリックします。

「統合タイプ」で「Lambda関数」を選びます。「Lambdaプロキシ統合の使用」にチェックを入れます。「Lambdaリージョン」で先ほどLambda関数をデプロイしたus-east-1を選択します。

「Lambda関数」の入力枠をクリックするとus-east-1にデプロイされているLambda関数の一覧が選べるので、「soracom-linedc-handson-20220128」を選びます。

全ての選択と入力が終わったら「保存」を押します。

以下の警告が出るので、「OK」を押します。

以下のような画面になれば成功です。

「アクション」のプルダウンから「APIのデプロイ」を選択します。

「デプロイされるステージ」で「[新しいステージ]」を選び、「ステージ名」に「prod」と入れ、「デプロイ」を押します。

下図のようになったら成功です。「URLの呼び出し」の所にあるURLがMessaging APIチャネルに設定するエンドポイントになりますので、これをコピーします。

コピーしたURLの後ろにあなたのMessaging APIチャネルのチャネルIDをつけ、Messaging APIチャネルの設定でWebhook URLに登録して動作確認を行ってください。

トラブルシューティング

設定がうまく進まないときにご覧ください。ここまでの作業が出来ている場合は次に進んでください。

・インポートできない場合 : 指定したファイルが誤っていないか確認をお願いします。

・Lambda関数が出てこない : API GatewayがあるリージョンとLambdaのリージョンが異なっている場合、Lambda関数が出てきません。同一のリージョンで作業するようにしてください。

放置しておいても、課金が発生することはありませんが、後片付けを行います。

LINE Messaging APIチャネルの削除(全員)

LINE Developerコンソールで、作成したプロバイダーの「Action」をクリックします。

作成したチャネルをクリックします

チャネル画面下部の「削除」をクリックします。

ダイアログで「LINE Official Account Managerを表示」をクリックします。

遷移した画面で、「上記の注意事項を理解して、アカウントの削除に同意します」のチェックボックスにチェックを入れ、「アカウントを削除」をクリックします。

ダイアログで「削除」をクリックします。

チャネル一覧に何も表示されなくなるまで、上記の作業を繰り返します。

プロバイダー設定タブを選んで、下部のプロバイダーの削除にある「削除」をクリックします。

ダイアログで、プロバイダーIDを入力し、「上記に同意し、このプロバイダーの永久削除を希望します。」のチェックボックスにチェックを入れて、「削除」をクリックします。

以上で、プロバイダーの削除は完了です。

AWS Lambda関数の削除(中級をやった方)

Lambdaのコンソール画面で、「soracom-linedc-handson-20220128」のチェックボックスにチェックを入れて、右上の「アクション」プルダウン内の「削除」をクリックします。

ダイアログで、関数を確認の上、「削除」をクリックします。

Amazon API Gatewayの削除(中級をやった方)

API Gatewayのコンソールにて、「linebot-incoming-for-handson-20220128」のラジオボタンを選択し、右上の「アクション」プルダウン内の「Delete」をクリックします。

ダイアログでAPI名が確認の上、「削除」をクリックします。

IAMロールの削除(中級をやった方)

IAMのコンソールを開いて左のペインから「ロール」をクリックします。

検索欄に今回作成したLambda関数名の「soracom-linedc-handson-20220128」を入れたときに表示されるIAMロールにチェックを入れて「削除」ボタンをクリックします。

ダイアログが表示されたらテキストフィールドにロール名を入力して「削除」ボタンをクリックします。

ここまでで、ハンズオンは終了です。お疲れ様でした!

デバイスについて

Keigan Motorという、モーターです。BLE、USB(UART)、I2Cで操作できます。Wio LTEとはI2Cで接続しています。

デバイスの操作を行っているマイコンについて

Wio LTE JP Versionという、LTE通信内蔵のプロトタイプ向けSTM32マイコンです。Arduino IDEで開発できます。AWS IoT CoreとはSORACOM Beamを経由してMQTT(S)でつながります。即ち、デバイス側のI2Cと、クラウド側のMQTTを橋渡しする役割です。

プログラム(Arduino IDEでは "スケッチ" と呼ばれている) はこちらで公開しています。

ポイントは mqtt_subscribe_callback 関数です。この関数が「AWS IoT Core からのコマンドを振り分けて処理する」役割を持っています。抜粋したのが以下です。

AWS IoT Core からのデータは moveByDistanceDegree といった文字列で送信されてくるため、それをマップ(対応表)を作っておき、そこから実際の処理に振り分けています。

void mqtt_subscribe_callback(...) {
  std::map<std::string, int> cmd;
  cmd["readMotorMeasurement"] = 1;
  cmd["moveByDistanceDegree"] = 2;
  cmd["reboot"] = 65535;

  switch(cmd[doc["method"]]) {
    case 1: // デバイスの状態報告
      reportMotorMeasurement();
      break;
    case 2: // 回転処理
      km_moveByDistanceDegree(doc["degree"], doc["rpm"]);
      break;
    case 65535: // 遠隔からのリセット
      km_reboot();
      delay(5000);
      mcu_restart();
      break;
  }
}

手元に動かせるデバイスが無くとも、例えば Wio LTE上のフルカラーLEDを光らせる(メソッドは Wio.LedSetRGB()) といった事もできるでしょう。

AWS IoT Coreについて

AWS IoT Coreは、IoT デバイスと AWS 内のサービスをつなげるゲートウェイ(中継)サービスです。機能としては、MQTTブローカー(サーバー)や、着信データに応じてAWSサービスを実行する "ルール(ACT)" 、デバイスの状態を保存できる "デバイスシャドウ" 、デバイス上でLambda関数を動かすことできるAWS IoT Greengrassの制御やデバイス上の関数更新の指示を出せたりと、まさに "ブローカー (仲介者)" です。

Web と AWS をつなげるサービスが Amazon API Gateway ならば、IoT デバイスと AWS をつなげるのが AWS IoT Core と言えるでしょう。

今回はMQTTブローカーとしてのみ利用しています。

異なるアカウントからAWS IoT Coreを呼び出す方法

今回はそれぞれのAWS Lambda 関数から、運営(というか、Maxの個人アカウント)のAWS IoT Coreを呼び出すようにしています。

AWS IoT Core側(すなわち、MaxのAWS アカウント側)では、以下を行っています。

これらが、Lambda関数作成者に DEST_** として配布された情報です。

Lambda 関数では boto3.client() 呼び出し時に、クレデンシャルを指定するところがポイントです。特に regon_name を忘れると失敗します、なんででしょうね。

import boto3
iot = boto3.client('iot-data',
  region_name=os.getenv('DEST_REGION_NAME'),
  endpoint_url=os.getenv('DEST_ENDPOINT_URL'),
  aws_access_key_id=os.getenv('DEST_AWS_ACCESS_KEY_ID'),   aws_secret_access_key=os.getenv('DEST_AWS_SECRET_ACCESS_KEY')
  )
  topic = 'wiolte-797973/command'
  payload = {}
  iot.publish(topic=topic, payload=json.dumps(payload, ensure_ascii=False))

SORACOM Beam について

SOARCOM Beamは、データ転送サービスです。主な機能はプロトコル変換と、認証情報のオフロード(肩代わり)です。今回はWio LTE と IoT Core の間に入り、MQTTをMQTTS(MQTT+TLS)化するプロトコル変換と、IoT Coreに接続する際に必要となるX.509認証の肩代わりで利用しています。

SORACOM Beam が無い場合、Wio LTEでは MQTTやデバイス制御のコード に加えて、 TLS 化や X.509認証のコード、そしてX.509証明書をマイコン内に書く必要があります。また、TLSは暗号化オーバーヘッドがあるため通信トラフィックも増えます。

SORACOM は SIM によって通信ができている時点で認証が済んでいるため、その情報を基にクラウド連携に必要なプロトコル変換や認証情報の付与が可能です。

このため、デバイス上からプロトコル実装(今回はTLS化)や、認証実装(今回はX.509証明書)が不要にできるため、デバイス開発を容易にします。

実際、今回のコードにTLS実装をする場合、1000行近くのライブラリ(mbedTLS)を入れる必要があります。

目的に合わせた SORACOM のクラウド連携サービス

本格的な利用に向けて ~ x-line-signature のチェック方法

LINE Messaging API から Amazon API Gateway へ Webhook を送信する際、Webhook が正当な呼び出しなのかをAPI 側で検証する方法が x-line-signature HTTP ヘッダです (仕様はこちら)。

AWS 上では、検証をするチャンスは2つあります。1つ目は Amazon API Gateway の「Lambda 認証 (Lambda オーソライザー)」 、2つ目は AWS Lambda 関数内です。本ハンズオンでは AWS Lambda 関数内で行いました。(検証は以下です。)

# get channel secret and check x-line-signature
cs = get_line_channel_secret(is_testing)
body = event['body']
sig = event['headers']['x-line-signature']
if not check_x_line_signature(cs, body, sig):
    print('## Does not match x-line-signature, done.')
    return {'statusCode': 403, 'body': 'Does not match x-line-signature'}

ハンズオンではわかりやすさを重視し、実処理を行う Lambda 関数内で検証も行いましたが、実用的にするには Amazon API Gateway の Lambda 認証の利用を強くお勧めします。

といっても、実質は Lambda 関数にて検証するわけですが、Lambda 認証にすることで以下の利点とリスク回避が可能です。

直接の実装は提示しませんが、参考になる実装としてAmazon API Gateway の Lambda オーソライザーで SORACOM Beam の署名検証を紹介しておきます。Python 2.7実装なので、3系でのチャレンジを是非とも。

AppSheet から Amazon DynamoDB の読み書きを行う

ノーコードで Web アプリが作れるAppSheetは、データソースに Amazon DynamoDBを指定できます。以下の通りです。

AppSheetに引き渡す必要のあるIAM ポリシーは以下の通りです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "dynamodb:ListTables",
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:DeleteItem",
                "dynamodb:Scan",
                "dynamodb:Query",
                "dynamodb:UpdateItem"
            ],
            "Resource": "arn:aws:dynamodb:*:*:table/YOUR_TABLE_NAME"
        }
    ]
}

AppSheetのカラム

AppSheetからAmazon DynamoDB のテーブルを利用する場合のカラムですが、既にテーブルに保存されているレコードからカラムを生成します。そのため、1件以上のレコードを入れておく必要があります。生成はDynamoDB テーブルの初回参照時、もしくは "Regenerate Structure" で生成してくれます。

以上