Renesas RXファミリ用 FreeRTOS-LTS (iot-reference-rx) でAWS IoT Core Device Locationを試してみる

# はじめに
今回はAWS IoT Coreの新機能であるAWS IoT Core Device Locationを試してみました。
これを使用すればCK-RX65N with RYZ014Aの大体の位置を緯度経度で知ることができます。

AWS IoT Core Device LocationについてもAWS公式で使い方が公開されています。
今回はこの使い方をベースにデモを実装しました
* [新しい AWS IoT Core Device Location 機能の使い方](https://aws.amazon.com/jp/blogs/news/how-to-get-started-with-the-new-aws-iot-core-device-location-service/)

実装したデモは以下GitHubリポジトリに公開しています。
* [nizuki926/iot-reference-rx](https://github.com/nizuki926/iot-reference-rx)
* Branch Name : v202210.01-lts-rx-1.1.2-device-location

上記使い方は位置の計測結果をIoTデバイスにPublishしています。
今回は簡易的なデモでCK-RX65Nに接続されたCellular-RYZ014Aモジュールから、IPアドレスとアクセスポイントの情報を取得して、JSON形式でAWSにMQTTメッセージとして送信するだけです。
その後、AWS IoT Coreのマネジメントコンソールから送信されたJSON形式のメッセージを緯度経度に変換するまでを行います。

```mermaid
sequenceDiagram
  actor U as User
  participant D as CK-RX65N
  participant C as RYZ014A module
  participant A as AWS
  D->>C: Request IP Adrress
  C->>D: Send IP Address
  D->>C: Request AP information
  C->>D: Send AP information
  D->>A: Publish IP Address and AP information as a MQTT message
  A->>U: Show MQTT messages
  U->>A: Resolve device location information
```

準備した環境は以下の通りです。

1. CK-RX65N (評価ボード)
2. e2 studio 2023-10 (統合開発環境)
3. CC-RX V3.05.00 (RXマイコン用コンパイラ)

# デバイス位置情報取得デモの実装
## 各デモタスクを起動するメインタスクを変更する
各デモタスクを起動するメインタスクを変更します。
今回は`ENABLE_DEVICE_LOCATION_DEMO`オプションを新規作成して、このオプションが有効 (`1`) な時に`PubSub`デモの代わりに`デバイス位置情報取得`デモを起動するように変更しました。

```c:main.c
	if(ApplicationCounter(Time2Wait))
	{
		/* Remove CLI task before going to demo. */
		/* CLI and Log tasks use common resources but are not exclusively controlled. */
		/* For this reason, the CLI task must be deleted before executing the Demo. */
		vTaskDelete(xCLIHandle);

		if( !Connect2AP())
		{
			configPRINTF( ( "Cellular init failed" ) );
		}
		else
		{

			vTaskDelay(300);

			configPRINTF( ( "Initialise the RTOS's TCP/IP stack\n" ) );

			configPRINTF( ( "---------STARTING DEMO---------\r\n" ) );

        #if (ENABLE_FLEET_PROVISIONING_DEMO == 1)
           vStartFleetProvisioningDemo();
        #else
           xSetMQTTAgentState( MQTT_AGENT_STATE_INITIALIZED );
        #endif

        vStartMQTTAgent (appmainMQTT_AGENT_TASK_STACK_SIZE, appmainMQTT_AGENT_TASK_PRIORITY);

#if (ENABLE_DEVICE_LOCATION_DEMO == 0)
        vStartSimplePubSubDemo ();
#else
        vStartDeviceLocationDemo ();
#endif

        #if (ENABLE_OTA_UPDATE_DEMO == 1)
                  vStartOtaDemo();
        #endif
		}
	}

	while( 1 )
	{
		vTaskSuspend( NULL );
	}
```

## 位置解決に必要な情報を取得する関数の実装
`main.c`に位置解決に必要な情報を取得する関数を実装します。
なぜ`main.c`に実装するのかというと使用する通信I/Fごとに取得できる情報に違いがあるからです。
今回はRYZ014Aを使用したセルラー通信のプロジェクトにデモを実装したので、IPアドレスとアクセスポイントの情報を使用することができます。
これが仮にイーサネット通信のプロジェクトにデモを実装すると、IPアドレスのみを使用できます。
したがって、通信I/Fごとに存在する`main.c`に関数を実装しました。
ちなみに、AWS IoT Core Device Locationで使用できる情報を以下の通りです。

* Wi-Fi ベースのソルバー
* セルラーベースのソルバー
* IP リバースルックアップソルバー
* GNSS ソルバー

IPアドレスとアクセスポイントの情報を取得するためにはRYZ014AドライバのAPI関数を呼び出していますが、セルラーベースで必須情報である`MCC`と`MNC`だけは取得することができないようです。
そのため、今回は`MCC`と`MNC`を固定値にして実装しています。
したがって、本デモの動作を試す場合は使用するSIMカードに応じて、`CELLULAR_MCC`と`CELLULAR_MNC`の値を変更してください。

```c:main.c
int vGetDeviceLocationInfo( char * buffer)
{
    e_cellular_err_t cell_ret;
    st_cellular_notice_t cellular_notice = {0};
    st_cellular_ipaddr_t ip_addr;

    cell_ret = R_CELLULAR_GetAPConnectState(&cellular_ctrl, CELLULAR_ENABLE_NETWORK_RESULT_CODE_LEVEL2, &cellular_notice);
    if (CELLULAR_SUCCESS != cell_ret)
    {
        return -1;
    }

    cell_ret = R_CELLULAR_GetPDPAddress(&cellular_ctrl, &ip_addr);
    if (CELLULAR_SUCCESS != cell_ret)
    {
        return -1;
    }

    return snprintf(buffer, demoDEVICE_LOCATION_JSON_SIZE, demoDEVICE_LOCATION_JSON, ip_addr.ipv4, CELLULAR_MCC, CELLULAR_MNC, strtol( cellular_notice.cell_id, NULL, 16 ), strtol( cellular_notice.ta_code, NULL, 16 ) );
}
```

## デモタスクの実装
今回実装した`デバイス位置情報取得`デモタスクは`PubSub`デモを流用しました。
あらかじめ、Cellular-RYZ014AモジュールからIPアドレスとアクセスポイントの情報を取得しておき、一定時間おきにAWSにJSON形式に変換してMQTTメッセージとして送信するだけのデモです。
`main.c`側に実装した`vGetDeviceLocationInfo`を呼び出して位置解決に必要な情報を取得しています。

```c:device_location_task.c
    if( xStatus == pdTRUE )
    {
        /* Create a topic name for this task to publish to. */
        xOutTopicLength = snprintf( cOutTopicBuf,
                                    mqttexampleOUTPUT_TOPIC_BUFFER_LENGTH,
                                    mqttexampleOUTPUT_TOPIC_FORMAT,
                                    gKeyValueStore.table[KVS_CORE_THING_NAME].value );

        /*  Assert if the topic buffer is enough to hold the required topic. */
        configASSERT( xOutTopicLength <= mqttexampleOUTPUT_TOPIC_BUFFER_LENGTH );

        /* Create a payload to send with the publish message.  This contains
         * the task name and an incrementing number. */
        do
        {
            xPayloadLength = vGetDeviceLocationInfo( cPayloadBuf );
        } while (-1 == xPayloadLength);

        /* For a finite number of publishes... */
        for( ulPublishCount = 0UL; ulPublishCount < mqttexamplePUBLISH_COUNT; ulPublishCount++ )
        {
            /* Assert if the buffer length is not enough to hold the message.*/
            configASSERT( xPayloadLength <= mqttexampleSTRING_BUFFER_LENGTH );

            LogInfo( ( "Sending publish request on topic \"%.*s\"", xOutTopicLength, cOutTopicBuf ) );

            xMQTTStatus = prvPublishToTopic( xQoS,
                                             cOutTopicBuf,
                                             xOutTopicLength,
                                             ( uint8_t * ) cPayloadBuf,
                                             xPayloadLength,
                                             mqttexampleNUM_PUBLISH_RETRIES );
```

# デモの動作確認手順
## モノの作成および初回の証明書/秘密鍵のダウンロード
AWSに接続してMQTTメッセージを送るために証明書/秘密鍵をダウンロードします。
下記`iot-reference-rx`における`Getting_Started_Guide.md`の`Step 4-1: Run PubSub demo`記載の方法に従って、モノの作成および証明書/秘密鍵をダウンロードしてください。

* [Getting_Started_Guide.md - Step 4-1: Run PubSub demo](https://github.com/renesas/iot-reference-rx/blob/main/Getting_Started_Guide.md#step-4-1-run-pubsub-demo)

## プログラムの実行
`v202210.01-lts-rx-1.1.2-device-location`ブランチからプロジェクトおよびソースコードをクローンしてから、e2 studioに`aws_ryz014a_ck_rx65n`をインポートしてください。
使用するSIMカードに応じて、Cellular用の情報を設定してプログラムをビルドします。
その後、プログラムを実行して、下記`step-4-1-6-input-via-cli-for-pubsub-demo`に従って、TeraTermで`モノの名前`、`エンドポイント名`、`証明書`、`秘密鍵`を入力してリセットします。

* [Getting_Started_Guide.md - step-4-1-6-input-via-cli-for-pubsub-demo](https://github.com/renesas/iot-reference-rx/blob/main/Getting_Started_Guide.md#step-4-1-6-input-via-cli-for-pubsub-demo)

リセット後、数秒間経過するとデモが実行されます。
その前に、AWSマネジメントコンソール側のMQTTテストクライアントでCK-RX65Nから送信されたMQTTメッセージを待ち受けましょう。
トピックのフィルターは`#`でサブスクライブすれば良いと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/7628c4f0-8a1e-e36b-9156-3c3f316fe846.png)

最初に入力した`モノの名前`、`エンドポイント名`、`証明書`、`秘密鍵`を使用してAWSに接続した後に下記のようなトピックでAWSにMQTTメッセージを送信します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/62fc192e-290d-9c4d-e65b-6906e0f0b800.png)

MQTTテストクライアントを確認するとCK-RX65NからPublishされたメッセージを確認できます。
この情報をコピーしてデバイスの位置情報の`ペイロードを介して位置を解決する`にこの情報を張り付けて解決を押しましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/6f7c1a47-1fb0-b940-ff72-c35d30f5bc34.png)

すると`リソースの場所`に以下のような情報が表示されます。
`coordinates`に表示されるのが緯度経度の情報です。
この情報を`Google map`等の地図情報サービスに張り付けることでIoTデバイスの大体の位置情報を知ることができます。
私の場合、誤差`500m`でIoTデバイスの大体の位置を取得することができました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/ef530e32-bb7e-f00e-c75d-86dcd68a914f.png)

# さいごに
今回はAWSの新機能であるAWS IoT Core Device LocationをRXファミリ用のFreeRTOS-LTSで試してみました。
イーサネットプロジェクトでも同じように試すことができると思いますが、家庭のLAN環境だとCK-RX65Nがルーターにぶら下がった状態であり`プライベートIPアドレス`が割り当てられるため、AWS IoT Core Device Locationが使用できないと考えて、今回Cellular-RYZ014Aプロジェクトにデモを実装しました。
イーサネットプロジェクトでも`グローバルIPアドレス`を知るためのWebサービス等を使用すれば同じようなことが実現可能だと考えます。
またさらに精度を高めるためには`GNSS スキャン結果メッセージ`をAWSに送信して、AWS IoT Core Device Locationの位置解決の情報として使用することができます。