aptpod Tech Blog

株式会社アプトポッドのテクノロジーブログです

SDK入門⑥〜最速最高度で計測する日〜

intdash SDKを使って開発したプログラムを自動で実行したいみなさん、

こんにちは、ソリューションアーキテクトの伊勢です。

これまで本シリーズではプログラムをコマンドで手動起動してきましたが、検証や運用では自動で起動したい場面が多いはずです。

今回はWebhookとLambdaを組み合わせて、計測完了直後にプログラムを起動する方法を紹介します。

はじめに

全体構成

SDK入門②の距離算出プログラムが計測完了直後に起動されるようにします。

距離算出

計測イベントをWebhookリクエストとしてAPI Gatewayに送信します。

Webhookリクエストを受けたAPI GatewayがLambdaを起動します。

レスポンス返却Lambdaは、リクエストが計測完了のときに距離算出Lambdaを起動します。1

距離算出Lambdaは、プログラムの完了をSlackで通知します。

Webhookとは

システム内イベントをシステム外にHTTPで通知する仕組みです。

これでintdash内の変化をintdash外からリアルタイムに知ることができます。

イベントドリブンなシステム連携により、効率的なワークフローを実現できます。

tech.aptpod.co.jp

今回はよりシンプルな使い方を紹介します。

  • Lambda関数をインラインコードで作成
  • intdash SDKのLambdaレイヤーを作成
  • ローカルPCでテストコードによる動作確認2

AWS Lambdaとは

言わずと知れたサーバレスでコードを実行できるコンピューティングサービスです。

aws.amazon.com

従量課金で手軽であり、作成した関数をイベントで起動できます。

今回はWebhookリクエストをAPI Gatewayで受けてLambdaを起動します。

Lambdaレイヤーとは

Lambda関数の実行環境に共有ライブラリや依存ファイルを追加する仕組みです。

これにより、Lambda関数が軽量化され、コードの再利用性が向上します。

今回はintdash SDKや依存するPythonパッケージを登録します。

docs.aws.amazon.com

API Gatewayとは

APIエンドポイントへのアクセス仲介サービスです。

WebリクエストをバックエンドのLambdaやEC2などにルーティングします。

やってみた

AWS構築

起動される側(全体構成の右側)から順に構築していきます。

距離算出Lambda
カスタムLambdaレイヤー作成

Lambdaレイヤーとして登録するためのZIPファイルを作成します。

ローカルPCでLambdaレイヤーのディレクトリ構造にSDK入門①SDK入門②のPython依存パッケージをインストールします。3

mkdir -p path/to/workdir/python/lib/python3.12/site-packages
pip3.12 install pydantic python-dateutil urllib3 -t path/to/workdir/python/lib/python3.12/site-packages
pip3.12 install protobuf -t path/to/workdir/python/lib/python3.12/site-packages

依存パッケージインストール

以下をZIPにまとめます。

  • SDK入門①で生成したintdash SDKパッケージintdash
  • SDK入門②で生成したProtocol Buffersエンコーダーgen
  • インストールしたPython依存パッケージ
cp -r intdash gen path/to/workdir/python/lib/python3.12/site-packages
cd path/to/workdir
find . -name "*.pyc" -delete
find . -name "__pycache__" -type d -exec rm -r {} +
zip -r intdash_sdk.zip python
ls -l intdash_sdk.zip python

LambdaレイヤーZIP作成

ZIPファイルをアップロードしてレイヤーを作成します。

Lambdaレイヤーの作成

Lambda関数作成

サンプルプログラムからLambda関数のZIPファイルを作成します。

今回のサンプルプログラムはLambdaで実行されることを想定しています。4

cd lesson6/intdash-distance/src
find . -name "*.pyc" -delete
find . -name "__pycache__" -type d -exec rm -r {} +
zip -r ../deployment_package.zip .

距離算出Lambda ZIPファイル作成

Lambda関数を作成します。

作成したZIPファイルをアップロードします。

距離算出Lambda関数作成

タイムアウト設定がデフォルト3秒だと途中終了してしまうため、5分に延長します。

距離算出Lambda タイムアウト設定

プログラムに渡す環境変数を設定します。

  • API_TOKEN: サーバー環境にアクセスするAPIトークン
  • API_URL: サーバー環境のURL
  • FETCH_SIZE: データポイントを何件ずつ処理するか
  • ORIGIN_LAT: 基準点(緯度)
  • ORIGIN_LON: 基準点(経度)
  • SLACK_URL: 通知先Slack

距離算出Lambda 環境変数設定

Lambdaレイヤーを追加します。

  • intdash SDK: 作成したカスタムLambdaレイヤー
  • requests: 公開ARNを指定

Lambdaレイヤーの追加

レスポンス返却Lambda
Lambda関数作成

intdashにレスポンスを返却するためのLambda関数を作成します。

lesson6/invoke-distance/src/lambda_function.pyの内容を貼り付けます。

レスポンス返却Lambda作成

プログラムに渡す環境変数を設定します。

  • SECRET_KEY: あとでWebhook設定に登録する任意の文字列

レスポンス返却Lambda 環境変数設定

API Gateway作成

POSTリクエストを受けるAPI Gatewayを作成します。

レスポンス返却Lambdaと統合します。

API Gateway作成

Webhook設定

設定はintdash APIで管理され、SDKでも参照・更新メソッドが提供されます。

ラップするCLIツールも作ってみました。

このツールを使って今回使う設定を登録・確認します。5

Webhook設定ツール

設定登録

API GatewayにWebhookリクストを送信する設定を追加します。

リクエストボディはこのようなJSONです。6

{
    "url": "https://example.execute-api.ap-northeast-1.amazonaws.com/webhook",
    "secret": "stringstringstringstringstringst",
    "edges_event": false,
    "measurements_event": true,
    "users_event": false,
    "edge_connections_event": false,
    "project_edges_event": false,
    "project_members_event": false
}

secretにはレスポンス返却Lambdaの環境変数SECRET_KEYを設定します。7

計測イベントを送信するようにmeasurements_eventのみtrueにします。8

JSONファイルをsrc_pathに指定します。

python lesson6/cli/src/hook_cli.py --api_url https://example.intdash.jp --api_token <YOUR_API_TOKEN> --project_uuid <YOUR_PROJECT_UUID> import --src_path=<YOUR_SRC_PATH>
設定確認

登録結果を一覧で確認します。

python lesson6/cli/src/hook_cli.py --api_url https://example.intdash.jp --api_token <YOUR_API_TOKEN> --project_uuid <YOUR_PROJECT_UUID> list

Webhook設定登録

計測

計測対象

せっかくなので、その場でプログラムを手動実行できない環境で試しました。

iOSアプリ intdash Motion で飛行中のGPSを計測します。

福岡出張の帰路

エアバスA320の最大巡航速度はマッハ0.82、国内線の高度はおよそ1万mです。

当社がこれまで計測した環境としては最速かつ最高度ではないかと思います。

1回の計測距離として最長かもしれません。

計測開始

iPhoneはフライトモードにしてあるため、計測がオフラインで開始されます。

オフライン計測を開始

機内Wifiが使える機体なら、リアルタイム計測も可能かもしれません。

離陸

計測完了

着陸後、Motionから計測を遅延アップロードします。

遅延アップロード

数十秒ほどでSlackに通知がありました。

Slack通知

PCからData Visualizerにリンクするとプレイバック再生が始まります。

成田空港を基準点とした算出距離が計測とあわせて表示されます。9

最高時速1,126km、最高高度9,597m、移動距離575km

サンプルプログラム説明

Webhook設定ツール

一覧

List Project Hookの結果のitems配列を返します。

        return self.api.list_project_webhooks(
            self.project_uuid, per_page=per_page
        ).items
取得

List Project Hookでhook_uuidを指定しています。10

        hooks = self.api.list_project_webhooks(
            self.project_uuid,
            hook_uuid=[hook_uuid],
        )
        return hooks.items[0]
登録

引数hook_uuidの指定有無でCreate/Update Project Hookを使い分けます。

        if hook_uuid:
            hook = self.api.update_project_webhook(
                self.project_uuid, hook_uuid, hook_project_update_request=hook_src
            )
        else:
            hook = self.api.create_project_webhook(
                self.project_uuid, hook_project_create_request=hook_src
            )
テスト

指定したresource_typeactionをHookリクエストとして送信します。11

        return self.api.test_project_webhook(
            self.project_uuid,
            hook_uuid,
            hook_test_request={"resource_type": resource_type, "action": action},
        )

距離算出

距離算出プログラム

APIアクセス部はSDK入門②と同じです。

Lambdaにあわせてメイン関数をlambda_handerに変更してあります。

定数は環境変数としてLambda設定から与えられるようにしてあります。

    api_url = os.getenv("API_URL", "https://example.intdash.jp")
    api_token = os.getenv("API_TOKEN", "<YOUR_API_TOKEN>")
    fetch_size = int(os.getenv("FETCH_SIZE", 100))
    origin_lat = float(os.getenv("ORIGIN_LAT", 35.6878973))
    origin_lon = float(os.getenv("ORIGIN_LON", 139.7170926))
    origin = (origin_lat, origin_lon)  # 会社
    slack_url = os.getenv("SLACK_URL", "<YOUR_SLACK_WEBHOOK_URL>")

テストコードから環境変数を与えて起動しています。12

        os.environ["API_URL"] = "https://example.intdash.jp"
        os.environ["API_TOKEN"] = "<YOUR_API_TOKEN>"
        os.environ["FETCH_SIZE"] = "100"
        os.environ["ORIGIN_LAT"] = "35.6878973"
        os.environ["ORIGIN_LON"] = "139.7170926"
        os.environ["SLACK_URL"] = "<YOUR_SLACK_WEBHOOK_URL>"

なお、距離算出Lambaが計測を完了するときにもWebhookリクエストが送信されます。これにより、距離算出Lambdaがもう一度起動されるため、無限ループの防止が必要です。

位置情報を含むデータポイントだけを取得します。

        stream = api.list_project_data_points(
            project_uuid=self.project_uuid,
            name=self.meas_uuid,
            data_id_filter=["#:1/gnss_coordinates"],
            start=self.start,
            limit=fetch_size,
            time_format="ns",
        )

データポイントが0件の場合は作成した計測を削除します。

        # 計測削除(GPSデータなし)
        if not count:
            self.writer.delete_measurement()
            logging.info(f"Deleted measurement: {measurement_dst.uuid}")
            return
レスポンス返却プログラム

intdash APIとは直接関わりません。

距離算出Lambdaを起動します。

        response = lambda_client.invoke(
            FunctionName="intdash-distance",
            InvocationType="Event",
            Payload=json.dumps(body_dict),
        )

起動は0.1秒程度で終わり、すぐにレスポンスを返却します。

    return {
        "statusCode": 200,
        "body": json.dumps({"message": "Webhook received and Distance Lambda invoked"}),
    }

おわりに

今回は起動方法を実際の利用シーンに近づけてみました。

intdash SDKのLambdaレイヤーに登録しておけば、複数のLambda関数に適用できて便利です。

なお、Lambdaは最大実行時間が15分のため、もっと長い処理はFargateやEC2インスタンスを利用することになります。

リンク

本シリーズの過去記事はこちらからご覧ください。


  1. 10秒以内にレスポンスを返すため、Lambdaを二段構成にしています。intdashはWebhookリクエストのステータスを管理しており、10秒以内に通知先からレスポンスが返るとステータスを成功 succeeded に移行します。
  2. テストコードはGitHubで公開しています。
  3. 前回までにインストールしたパッケージをすべてZIP化すると、Lambdaレイヤーの上限10MBを超えてしまうためです。
  4. サンプルプログラムはGitHubで公開しています。
  5. CLIの使い方はGitHubで解説します。
  6. サンプルプログラム内にテンプレートlesson6/cli/config/hook.jsonを用意しています。
  7. HMAC検証でWebhookリクエストの改ざんを防止します。
  8. 間違えやすいですが、各項目名はxxx_eventsではなくxxx_eventです。
  9. 計測は1時間ほどで終了しています。Motionログイン時にサーバーで払い出すアクセストークンの有効期限によるものです。
  10. 設定を1件取得するget_project_webhookは現行バージョンでは動作しません。次バージョンにて改修予定です。
  11. SDK入門①で紹介しているintdash SDK for Python生成の手順ではcreated/updated/deleted以外のactionがエラーになるようです。その場合は、curlコマンドで直接Test Hook APIを叩いて確認します。
  12. テストコードの使い方はGitHubで解説します。