SORACOM Funkを使って受信データをAmazon S3にCSVで保存する

# はじめに
こちらは[SORACOM Advent Calendar 2023](https://qiita.com/advent-calendar/2023/soracom) 3日目の記事です。

https://qiita.com/advent-calendar/2023/soracom

昨日（12/2）は @kizawa2020 さんの（まさかの英語の！）会議脱出ボタングローバルバージョン！
あの大きいボタンを持っていないので、試すことはできませんが、Amazon Connectの箇所は参考になりそうなので、後日熟読したいと思います。

# 経緯
先日、[SORACOM Harvestに保存されたデータをAPI経由で取得し、CSVにする方法](https://qiita.com/ozk009/items/c880d234a4b91f4882d0)を紹介しましたが、有識者の方から「そのままCSVで保存すれば？」とアドバイスをいただき、それはそうだということでやってみました。

# お詫び
資料中のAWS等の設定でfun<font color="red"><b>k</b></font>とすべきところを凡ミスでfun<font color="red"><b>c</b></font>としてしまってっていました。
お恥ずかしい限りですが、ご容赦ください。

# 事前知識
SORACOMには[SORACOM Funk](https://soracom.jp/services/funk/)というサービスがあり、クラウドファンクションアダプタと呼ばれるもので、SORACOMでデータを受信したタイミングでAWSのLambdaを直接呼ぶことができます。
以下のドキュメントではFunkを使い、Lambdaを呼ぶ手順が説明されているので、こちらをベースにやり方を説明したいと思います。

https://users.soracom.io/ja-jp/docs/funk/aws-lambda/

今回の構成図は以下となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/ef55b25d-f2f0-b1cb-f7a8-6cd49d005fbf.png)

[GPSマルチユニットSORACOM Edition](https://soracom.jp/store/5235/)の位置情報（緯度経度）をSORACOM Funk、AWS Lambdaを経由して、Amazon S3にCSVファイルとして保存します。
保存したCSVはPCのブラウザなどでダウンロードできるようにします。

なお、GPSマルチユニットについては、あらかじめ[バイナリパーサーの設定](https://users.soracom.io/ja-jp/docs/binary-parser/predefined-format/#gpsmultiunit)をしておく必要があります。

# 手順
ざっくり説明すると、以下となります。
- [AWSの作業](#awsの作業)
    - [S3バケットの作成](#s3バケットの作成)
    - [Lambdaの作成](#lambdaの作成)
    - [IAMポリシー、ロールの作成](#iamポリシーロールの作成)
- [SORACOMの作業](#soracomの作業)
    - [認証情報の登録](#認証情報の登録)
    - [SIMグループの設定](#simグループの設定)

# AWSの作業
先にAWS側の作業です。
## S3バケットの作成
今回、お試しのため<font color="red">セキュリティ的には問題があります</font>が、S3にCSVを置いて、ダウンロードできるようにします。

1. [AWSコンソールのS3管理画面](https://s3.console.aws.amazon.com/s3/home?region=ap-northeast-1#)で「バケットを作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/3b40f7fd-fe45-f799-e59d-81e1b534c83e.png)

1. バケット名を入力し、リージョンを選択します。 この記事でバケット名は「soracom-func-s3-csv」としていますが、同じ名前は設定できないので、別の名前で作成してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/9e7070f2-7754-cbde-ca54-9d5ec8d56e77.png)

1. 下にスクロールし、「パブリックアクセスをすべてブロック」のチェックを外し、注記で「承諾します」をチェックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/d856235c-58ae-f75e-f052-8a2efcd8e828.png)

1. 下にスクロールし、「バケットを作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/03c96d49-58db-1ce4-a502-e3176d042550.png)

1. 作成後、アクセス許可からバケットポリシーの「編集」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/53d8a14d-1f51-ed81-7837-c1c729e2f6dc.png)

1. ポリシーのJSONを記入し、「関数の保存」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/dec26d8f-4402-ee86-5388-394dd97bf000.png)
JSONは以下となります。
    ```json
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "PublicReadGetObject",
                "Effect": "Allow",
                "Principal": "*",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::（バケット名）/*"
            }
        ]
    }
    ```
    実際の運用には以下のようなIPアドレス制限などでアクセス制限を行ってください。
    https://www.jpcyber.com/support/allow-s3-access-from-specific-ip
    <font color='red'>緯度経度情報から自宅の場所がバレるといったリスクがあるので、ご注意ください！</font>

1. S3のARNをメモしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/ba7eec1c-ff48-33f9-6892-f60e57a753bf.png)

## Lambdaの作成
続いてLambdaの作成を行います。

### コードの作成
1. [AWSコンソールのLambda管理画面](https://ap-northeast-1.console.aws.amazon.com/lambda/home?region=ap-northeast-1#/functions)で「関数の作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/be42ec63-a488-0752-5aad-bccc64e28903.png)

1. 「一から作成」を選択し、関数名を入力、ランタイム「Python 3.11」を選択し、「関数の作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/79785f28-cabe-51fc-5371-bcd08e6c71aa.png)

1. コードを記入し、「Deploy」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/568f91e3-c494-cff1-82b6-396ac07f81c3.png)

コードの内容は以下の通りです。
```python
import boto3
import datetime

def lambda_handler(event, context):
    print("Event:", event)
    print("Context:", context)

    # S3バケット名とファイル名を指定
    bucket_name = '（バケット名）'
    file_name = 'test.csv'

    # S3クライアントを作成
    s3 = boto3.client('s3')

    # 現在の日時を取得
    current_time = datetime.datetime.now() + datetime.timedelta(hours=9)
    formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
    
    # 緯度経度のデータ（イベントから取得または固定値で設定）
    latitude = event.get('lat', 35.6895)  # 例: 東京の緯度
    longitude = event.get('lon', 139.6917)  # 例: 東京の経度

    # CSV形式の文字列を作成
    new_data = f"{formatted_time},{latitude},{longitude}\n"

    # 既存のファイルをS3からダウンロード
    try:
        response = s3.get_object(Bucket=bucket_name, Key=file_name)
        current_data = response['Body'].read().decode('utf-8')
    except s3.exceptions.NoSuchKey:
        # ファイルが存在しない場合は空の文字列をセット
        current_data = ""

    # 新しいデータを追加
    updated_data = current_data + new_data

    # 更新されたデータをS3にアップロード
    s3.put_object(Body=updated_data, Bucket=bucket_name, Key=file_name)

    return {
        'statusCode': 200,
        'body': 'Data added successfully!'
    }
```
このコードはほぼChatGPTに作ってもらいました。
（いやー・・すごい時代になりました）

### 権限設定
続いてS3にアクセスするための権限設定です。

1. 設定タブを選択し、「アクセス権限」を選択してロール名のリンクをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/c48ee780-5612-e890-50ef-0bd1e2925a6e.png)

1. 「許可を追加」「ポリシーをアタッチ」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/c8e3fc32-ebee-9bef-f661-62af185c419c.png)

1. 検索欄に「s3」と入力すると、候補が出てくるので、「AmazonS3FullAccess」にチェックを入れ、「許可を追加」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/11ddb9c4-4848-2361-202b-8bdd380b5dc9.png)

1. 作成したLambda関数のARNをコピーしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/3a32e264-6583-454d-401b-064cb384a335.png)

### Lambdaのテスト
作成したLambda関数のテストをしてみます。
1. 「テスト」タブを選択し、イベントJSONに適当な緯度経度情報を入力して「テスト」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/8d2dfdf8-569d-eaa1-0f5a-5991c16fe9e1.png)
    テストデータのサンプル（JSON）は以下となります。
    ```json
    {
        "lat": 36.689,
        "lon": 139.691
    }
    ```
1. 前段階で作成したS3バケットに「test.csv」ができていればそちらを開いてみてください。JSONに記入した緯度経度が入っていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/f7d783fd-cf73-5066-784f-c4c5a0cafe80.png)

## IAMポリシー、ロールの作成
SORACOM FunkからLambdaを呼ぶためのIAMロールを作成します。

### ポリシーの作成
1. [AWSコンソールのIAM管理画面](https://us-east-1.console.aws.amazon.com/iam/home?region=ap-northeast-1#/home)で「ポリシー」を選択し、「ポリシーの作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/c918cac0-ab03-f5c2-ce72-f57f0f55aed1.png)

1. サービスを選択で「Lambda」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/9b94107f-efb4-1e9a-8325-5334d9ad19ba.png)

1. アクション許可「invokefunction」と入力すると書き込みで候補が出るので、チェックし、「ARNを追加」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/d953e2f6-cca5-5010-8339-98a3eaedf923.png)

1. 前段でメモしたLambdaのARNを入力し、「ARNを追加」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/4ca87de6-01ce-5ead-4a3e-689439ae6059.png)

1. 「許可をさらに追加」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/a1fec077-ae12-d096-8c26-276c50b9e748.png)

1. サービスを選択で「S3」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/2d40c341-9b27-8e7c-2ec6-b59ee6f7e88d.png)

1. ひとまず「すべてのS3アクション」を選択します（厳密にはちゃんと選択した方が良さそうです）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/3ccd0a3e-1c4d-6478-8d0b-e605601f2aaa.png)

1. リソースでbucketの「ARNを追加」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/ff85569c-c6c3-4b94-1a79-ff7da034b096.png)

1. 前段でメモしたS3バケットのARNを入力し、「ARNを追加」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/138579d3-a1d7-6b55-fb8e-ae862200bc96.png)

1. 戻った画面で「次へ」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/77237944-6c49-595c-ff21-1947c7094e53.png)

1. ポリシー名を入力し、「ポリシーを作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/7d3a2c84-c803-5c15-2fe1-2a5e3f5dbc74.png)

### ロールの作成
続いて作成したポリシーを割り当てたロールを作成します。

1. ロールの「ロールを作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/1ae74867-7466-40cb-603c-ad5bfcc63ccf.png)

1. AWSアカウントを選択し、「別のAWSアカウント」を選択してアカウントID「<b>762707677580</b>（日本カバレッジの場合）」または「<b>950858143650</b>（グローバルカバレッジの場合）」を入力し、オプションの「外部IDを要求する…」を選択して（セキュリティ上リスクありそうですが、[前述のドキュメント](https://users.soracom.io/ja-jp/docs/funk/aws-lambda/)と同じ）「<b>External-ID-Mej73gfhrFhimGKb</b>」を入力して「次へ」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/33fefc67-5abd-fc98-ce8a-5190d6401af6.png)

1. 続いて許可の追加画面で前段で作成したポリシー名を入力して、選択し、「次へ」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/f27e4dc4-bca9-74b7-7d16-16f8ba1531d3.png)

1. ロール名を入力し、「ロールを作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/8eb559d5-6cad-1e18-d6fc-515fd2bb2f49.png)

1. ロールのARNをメモします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/47cbfa23-d482-0662-e323-63f0cfe91b48.png)


# SORACOMの作業
つづいてSORACOM側の設定です。
## 認証情報の登録
FunkからLambdaを呼び出すための認証グループを作成します。

1. SORACOMコンソールで画面右上のアカウント名をクリックし、「セキュリティ」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/9e42ba64-1c73-e37f-e7de-ef582b843249.png)

1. 認証上ストアの「認証情報の登録」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/0ae6a005-5591-1cc2-d017-83d2b24a06fa.png)

1. 認証情報IDで任意の文字列を指定し、種別を「AWS IAMロール認証情報」を入力、ロールARNに前段でメモしたロールのARNを指定、外部IDに「External-ID-Mej73gfhrFhimGKb」を入力して登録をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/cea57f3d-e957-c8a3-f00a-6f41e7e21eb7.png)


## SIMグループの設定
最後にSIMグループの設定です。
該当SIMのSIMグループの編集画面を開き、SORACOM Funk設定をONにした後、サービスを「AWS Lambda」関数のARNをLambdaのARN、認証情報を先ほど作成した認証情報ID、送信データ形式を「JSON」にして保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/d83eeebb-34f9-5dc0-9855-1cf0debaed3f.png)

以上で設定完了です。

S3で作成したバケットのCSVのプロパティに「オブジェクトURL」がダウンロードする際に必要なURLです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/916611/c053f827-a6a6-79fb-755f-b9add519e71e.png)
ダウンロードしたいパソコンのブラウザからこのURLにアクセスすると、CSVファイルがダウンロードできます。

# まとめ
今回の手法ですが、尊敬する先輩、@tatsuya1970 さんにお誘いいただいて参加した[web3 Global Hackathon](https://app.akindo.io/hackathons/aA8Ek0mDGS8kdZoMe)というハッカソンで、自動車の走行経路を保存、取得する方法として使いました。
<b>※この実装ではGPSマルチユニットではなく、[スマホを使って](https://fugiters.net/blog/index.php/2021/12/23/bus_tracker2/)います。</b>

https://app.akindo.io/communities/27mmW8jKji236A0l/products/o677vWdgqUAAdLXK


残念ながら1次審査通過ならずでしたが、今回の記事を書くきっかけとなりましたし、まだまだ勉強不足ですがWeb3という新しい技術に触れることができました。
このアドベントカレンダーもですが、イベント参加はスキルアップやアウトプットの良い機会となります。ありがたい限りです。

ということで全くアドベントカレンダー感が無い記事となってしまいましたが、どなたかの参考になれば幸いです。

明日は @anysonica さん、今年も多方面に大活躍でしたので、楽しみにしてます！
引き続き[SORACOM Advent Calendar 2023](https://qiita.com/advent-calendar/2023/soracom)をお楽しみください。