Renesas RA : OpenAIのChatGPT AIと会話

皆さん、OpenAIが提供しているChatGPTと呼ぶAIモデルをご存じでしょうか。
最近はTVニュースやバラエティ番組でも多く紹介されているので、エンジニアのみならず一般の方もご存じのことかと思います。

本記事ではOpenAIが提供しているWeb APIを使って、Renesas RAマイコンからこのAIと会話してみようと思います。

今回はRA MCU用[Flexible Software Package(FSP)](https://www.renesas.com/jp/ja/software-tool/flexible-software-package-fsp)に同梱のミドルウェアMicrosoft製[Azure RTOS NetX Duo](https://learn.microsoft.com/ja-JP/azure/rtos/netx-duo/) Web HTTPS Clientアドオンを使用してネットワーク通信機能を実装します。またマイコンからLINEサーバまでの間のインターネットにおいて、メッセージデータを安全に届けるため、TLS (Transport Layer Security)セキュリティを有効にします。

全体イメージ：
<img width="800" alt="ra-openai-overall-image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/13f8fac9-cf73-e609-8642-3101827ee1a5.png">

:::note warn
OpenAIのAPIは発展途上であり、仕様変更により以下紹介の方法では接続不可となる可能性があります。動作しない場合、OpenAIのAPIリファレンスガイドを参照して修正してください。
:::

:::note warn
記事内に誤った情報が含まれている可能性があります。
著者はこの記事にて生じた損害に関し一切の責任を負いません。
:::

# OpenAI API
OpenAIは人工知能（Artificial Interigence, AI）の研究開発、展開を行う会社です。
OpenAIはこれまで様々な強力なAIモデルを発表してきましたが、2020.05に発表したChatGPT3は入力したパラメータ（テキスト）に対して、人間が話すような非常に自然な返答をすることが可能になり、世界に衝撃を与えました。
さらに2023.03に発表されたChatGPT4はより学習精度を向上させ、画像入力に対応、司法試験の模擬試験の合格レベルの結果を出したなどさらなる進化を続けています。

OpenAIはそのAIモデルをHTTPプロトコルにのせて試せるAPIを公開しており、インターネットに接続できるIoT Edgeデバイスであれば、ChatGPTを活用できます。
OpenAI API Reference 👉 [URL](https://platform.openai.com/docs/api-reference)

:::note warn
OpenAIのAPIを使用するには、アカウント登録が必要です。
:::

# アプリ構成
今回はRenesas RA Family RA6M5 MCU上に、FSP + Azure RTOS NetX Duo Web HTTPSミドルウェアを使用したネットワークアプリケーションを実装します。NetX Duoを使用しますので、必然とRTOSはAzure RTOS ThreadXを使用することになります。

下図にアプリケーションで使用するFSPコンポーネントを赤枠で示します。
・ ネットワーク通信：NetX Duo & Addon (Web HTTPS Client、DHCP Client、DNS Client)、Ethernet HAL
・ ネットワークセキュリティ：NetX Secure、~~SCE (Secure Cryptographic Engine、ハードウェアアクセラレータ)~~
・ テストテキスト送信開始トリガ：External Interrupt HAL (ユーザスイッチの押し下げ検出)
・ LED状態表示：I/O Ports HAL
<img width="600" alt="ra-openai-fsp-usage.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/e5e21099-1ca6-af66-19f3-6c27b6347d16.png">
(FSPv4.3.0のコンポーネント一覧)

:::note warn
今回はFSPv4.3.0上に実装していますが、一部H/Wインプリに正常動作が確認できていない点があるため、仮でS/Wで代用している部分があります。追って確認しアップデートしていきます。
:::

ハードウェア接続：
<img width="600" alt="ra-openai-hardware-conn.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/6146da6e-d61f-6032-7209-37a7f5735a90.png">

アプリケーション動作フロー：
<img width="300" alt="ra-openai-system-flow.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/10f2c391-f298-9b49-29ca-98dba51108db.png">

以降の章ではRenesas製のRA6M5 MCU開発ボードEK-RA6M5を使用した開発手順を説明しますが、基本的には他MCUやボードにおいても同様の手順で開発できると思います。必要に応じて各MCUやボードの仕様に合わせて設定内容を変更してください。

また以下の手順例では、ネットワークインターフェースにEthernet（有線接続、Ether PHYはKSZ8091）を使用します。WiFiなど無線通信を使用する場合はFSPユーザーズマニュアルやExample Programを参照して実装してください。

# RAアプリケーション開発
それではe2studio開発環境とFSPのコンフィグレータを使用して開発を進めていきます。

:::note info
この記事のサンプルコード・プロジェクトをGithubで公開しています。
以下の手順でうまく設定できない場合や、0から作るのが億劫な方はそちらをご参照ください。
詳細は、[ここ](#サンプルコード)
:::

### 1.　プロジェクト生成
e2studio IDEを起動し、**File** > **New** > **Renesas C/C++ Project** > **Renesas RA** からプロジェクト生成ウィンドウを開く。
<img width="600" alt="ra-openai-1-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/74c0cc3d-3cf6-32f4-01c3-795f3f76f9c9.png">
**C/C++** > **Renesas RA C/C++ Project** を選択し、**Next >** をクリック。
<img width="600" alt="ra-openai-1-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/e6671dfd-49f5-b1ec-527c-e1a6f34cf6d9.png">
適当なプロジェクト名を入力。
<img width="600" alt="ra-openai-1-3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/bb654db1-224a-32e6-348b-d1e884942cd9.png">
デバイスを選択します。Renesas製ボードをお使いの場合、**Board**から選択。リストに表示されてないユーザ独自のボードを使用する場合は、**Board** は選択せず **Device** からMCUを選択。**Next >** をクリック。
<img width="600" alt="ra-openai-1-4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/7ce67d38-91b1-f168-e4e0-18aa6f38b5d6.png">
**Project Type** には **Flat Project** を選択。**Next >** をクリック。
<img width="600" alt="ra-openai-1-5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/c2a54828-3287-5e88-8b21-8a531309af58.png">
**Build Artifact** に **Executable** 、**RTOS Selection** に **Azure RTOS ThreadX (vX.X.X+fspvX.X.X)** を選択。**Next >** をクリック。
<img width="600" alt="ra-openai-1-6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/005474fc-803b-66c4-a0e4-886f5a56f242.png">
**Minimal** Project Templateを選択し、**Finish** をクリック。プロジェクト生成が始まります。
<img width="600" alt="ra-openai-1-7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/8fc073d7-c8f1-a7b6-80d5-8c77d8e195b2.png">

**FSP Concifugration** パースペクティブを開くか聞かれた場合は、**Open Perspective** を選択。
<img width="450" alt="ra-openai-1-8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/9daaa0e6-9c3b-61ba-6d17-756254502884.png">

### 2.　クロック&ピンコンフィグレーション
コンフィグレータの **Clocks** タブや **Pins** タブを開き、使用するクロック構成・ピン構成に合わせて設定を変更してください。今回のH/W構成はシンプルなため、ボードBSPに設定されているプリセットでカバーされているため、変更する必要はありません。
<img width="600" alt="ra-openai-1-9.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/3fc3fb9f-4b11-c521-8a14-5f5985e41594.png">

### 3.　スレッド追加
**Stacks** タブを開き、**Threads** 欄の**New Thread** をクリック。"New Thread"という名前のスレッドが追加されます。スレッドのシンボル名は"new_thread0"になります。
<img width="600" alt="ra-openai-1-10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/6c01c6aa-641e-0074-6a30-77d02055ed40.png">

**New Thread** を選択し、**Properties** ビューから**Thread** > **Stack Size** を"8192"に変更します。
<img width="600" alt="ra-openai-1-11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/8f9a9f36-6841-c852-d92b-6e9f5fe0059e.png">

### 4.　スタックコンフィグレーション
#### 4.1 Web HTTPS Clientモジュールを追加
**Stacks** タブ上で、**New Thread** を選択した状態で、**New Stack** > **Networking** > **Azure RTOS NetX Duo Web HTTP/HTTPS Client**を追加。
<img width="600" alt="ra-openai-1-12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/dd9fcb6c-341f-4692-8e82-4156083d5360.png">
以下のようにスタックツリーが表示されます。
<img width="600" alt="ra-openai-1-13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/be1a4a3e-6fe7-b928-2752-59373c085404.png">
"Add NetX Duo Network Driver"と表示されているブロックをクリックし、**New** > **NetX Duo Ethernet Driver (rm_netxduo_ether)** を選択。 
<img width="600" alt="ra-openai-1-14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/0a714bac-ff3c-9c5c-ec19-15f4a29e8cfe.png">
"Add NetX Duo Packet Pool"と表示されているブロックをクリックし、**Use** > **g_packet_pool0 Azure RTOS NetX Duo Packet Pool Instance** を選択。今回はNetX Duo IP Instance下にデフォルトで追加されているPacket Poolを共有利用します。**New** を選択すれば、分離したPacket Poolも構成可能です。
<img width="600" alt="ra-openai-1-15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/6c6c7456-ce02-d35d-e65a-e43970ddaa43.png">
"Add NetX Duo Secure"と表示されているブロックをクリックし、**New** > **Azure RTOS NetX Secure** を選択。
<img width="600" alt="ra-openai-1-16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/852dd93f-90c4-ac51-f93a-4f80065544b8.png">
Azure RTOS NetX Cryptoブロック下、"Add NetX Crypto..."と表示されているブロックをクリックし、**New** > **Azure RTOS NetX Crypto Software Only** を選択。
<img width="600" alt="ra-openai-1-17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/409a6942-9986-36ee-22b4-8ddf82eef293.png">

#### 4.2 Azure RTOS NetX Duo DHCP IPv4 Clientモジュールを追加
ネットワークからデバイスのIPアドレスを取得するDHCP Clientを追加します。
※固定IPを使用する場合はこのモジュールは必要ありません。
**New Stack** > **Networking** > **Azure RTOS NetX Duo DHCP IPv4 Client**を選択。
<img width="500" alt="ra-openai-1-18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/708f0852-2c4e-242d-2204-b4d877f2aab0.png">
NetX Duo DHCP IPv4 Clientモジュール下のブロックは、Web HTTP Clientのものを共有利用します。それぞれのブロックで　**Use** を選択。
<img width="400" alt="ra-openai-1-19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/b9ed3c8c-79a7-2577-f301-5501078254c9.png">
<img width="600" alt="ra-openai-1-20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/72546a1d-a87a-b660-a03e-83e47faffe21.png">

#### 4.3 Azure RTOS NetX Duo DNS Clientモジュールを追加
インターネット上のDNSサーバから、接続先のLINEサーバのIPアドレスを検索するDNS Clientを追加します。
※手動でIPアドレスを検索し、設定する場合はこのモジュールおよび関連コードは必要ありません。
**New Stack** > **Networking** > **Azure RTOS NetX Duo DNS Client** を選択。
<img width="500" alt="ra-openai-1-21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/0a13a0d1-3d24-ad14-3c75-10b2385372d7.png">
NetX Duo DNS Clientモジュール下のブロックは、Web HTTP Clientのものを共有利用します。それぞれのブロックで　**Use** を選択。
<img width="400" alt="ra-openai-1-22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/74b88eb5-df24-3dc1-9d51-d8583cb5b606.png">
<img width="600" alt="ra-openai-1-23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/a4a3cce2-4329-e804-c572-2be82b8a2834.png">

以上でネットワークに必要なモジュールは追加が完了しました。
#### 4.4 External IRQモジュールを追加
さらに外部端子割込み検出用のExternal IRQモジュールを追加します。
**New Stack** > **Input** > **External IRQ** を選択。
<img width="600" alt="ra-openai-1-24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/7333b1c9-ad2b-848c-7761-10074d26b278.png">

最終的には以下のようなスタックツリーになります。横長になってしまいました。。。
※現時点では、赤字のエラーが表示されていると思います。以降のステップで直していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/8b82b9d9-b604-c9ed-491b-244909964eb8.png)

#### 4.5 コンフィグレーション
**Azure RTOS NetX Crypto HW Acceleration** ブロックをクリックし、プロパティビューから **Common** > **Standalone Usage** を **Use with TLS** に変更。
<img width="500" alt="ra-openai-1-25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/8f5d28f5-99f2-0fb2-92a1-3e16743dae85.png">
**Azure RTOS NetX Duo Web HTTP/HTTPS Client** ブロックをクリックし、プロパティビューから **Common** > **Web HTTP** > **Client** を **Enable** に変更。
<img width="600" alt="ra-openai-1-26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/dc8a5e2e-613c-2539-8107-301d810d2a55.png">
**Azure RTOS NetX Duo Packet Pool** ブロックをクリックし、プロパティビューから **Module** > **Number of Packets in Pool** を64に変更。(念のため)
<img width="600" alt="ra-openai-1-27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/78d02f0e-a081-10ed-3500-833d11c9fa78.png">

External IRQの設定を変更します。EK-RA6M5のユーザスイッチS1はIRQ Channel 10に接続され、押された際はLowレベルとなります。**External IRQ** ブロックをクリックし、
**Channel** を "10"に、
**Trigger** を **Falling** に、
**Callback** を "irq_sw1_cb" に変更します。
<img width="500" alt="ra-openai-1-28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/26e34fed-0a9d-9b9d-1480-56eb9bbce4a6.png">


コンフィグレータの **BSP** タブを開き、プロパティビューから **RA Common** > **Main stack size** を"0x4000"に、 **Heap size** を"0x1000"に変更。
<img width="600" alt="ra-openai-1-29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/be251cc3-4cce-1678-afdc-b5d3124972f9.png">


コンフィグレーション完了です。**Generate Project Content** ボタンを押し、ソースコードと設定ファイルをプロジェクトに適用します。
<img width="200" alt="ra-openai-1-30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/9f69e951-3a04-ff3b-008a-3786dd0bf865.png">

### 5. コード実装：ベースアプリ
ベースのアプリケーションを実装します。動作フローは[ここ](#アプリ構成)を確認してください。

RTOS選択・Minimal Project Template選択でプロジェクトを作成した場合、\<thread name\>_entry.cというファイルがsrcフォルダに生成されます。このファイルにユーザプリケーションを書きます。

以下を参考にコードを実装してください。

:::note info
コード内には **TODO** と書いたコメント文を追加しています。以降のステップではこの部分にコードを追加していきます。
:::

```new_thread_entry.c
#include "new_thread0.h"

/* LEDs */
#define LED_1_PIN               BSP_IO_PORT_00_PIN_07 /* Green */
#define LED_2_PIN               BSP_IO_PORT_00_PIN_08 /* Red */
#define LED_ON_LEVEL            BSP_IO_LEVEL_HIGH
#define LED_OFF_LEVEL           BSP_IO_LEVEL_LOW

/* Debug output */
/* TODO(1): Add instruction conversion code for debug output */

#define INFO_PRINT(fn_, ...)     printf((fn_), ##__VA_ARGS__);
#define ERROR_ASSERT(a, b, c, d) if(a){ printf("  Error in " b ": 0x%x\n", c); \
                                 R_IOPORT_PinWrite(NULL, LED_2_PIN, LED_ON_LEVEL); d;}

/* TODO(2): Add additional variables definition */

volatile bool g_sw1_pushed = false;

void netx_packet_pool_setup(void);
void netx_ip_setup(void);
void netx_dhcp_client_setup(void);
void netx_dns_setup(void);
void server_ip_address_get(void);
void openai_inquiry_start(void);

/* TODO(3): Add function definition */

void new_thread0_entry(void)
{
    UINT status = 0;
    UINT sw_push_counter = 0;
    volatile bool g_led1_status = false;

    INFO_PRINT("\n==============================================\n");
    INFO_PRINT("Application thread started.\n");

    R_ICU_ExternalIrqOpen(&g_external_irq0_ctrl, &g_external_irq0_cfg);
    R_ICU_ExternalIrqEnable(&g_external_irq0_ctrl);

    /* Setup the platform; initialize the SCE and the TRNG */
    status = nx_crypto_initialize();
    ERROR_ASSERT(NX_CRYPTO_SUCCESS != status, "nx_crypto_initialize", status, __BKPT(0));

    /* Initialize NetX system */
    nx_system_initialize();

    /* Setup NetX packet pool memory */
    netx_packet_pool_setup();

    /* Setup NetX IP */
    netx_ip_setup();

    /* Setup NetX DHCP Client and get device ip address */
    netx_dhcp_client_setup();

    /* Setup DNS */
    netx_dns_setup();

    /* Get server ip address */
    server_ip_address_get();

    INFO_PRINT("All setup completed. Https client application is ready.\n");
    INFO_PRINT("Press SW1 or enter a text to start request sending to OpenAI.\n");
    INFO_PRINT("-----------------------\n");

    while (1)
    {
        if(g_sw1_pushed)
        {
            g_sw1_pushed = false;
            sw_push_counter++;

            /* Prepare message data */
            /* TODO(4-1): message preparation code */

            /* Send packet to OpenAI */
            openai_inquiry_start();
        }

        if(1/* TODO (4-2): Add RTT data queue check code*/)
        {
            /* Prepare message data */
            /* TODO(4-3): message preparation code */

            /* Send packet to OpenAI */
            openai_inquiry_start();
        }

        tx_thread_sleep (50);

        /* Blinks LED */
        g_led1_status = !g_led1_status;
        if(g_led1_status)
        {
            R_IOPORT_PinWrite(NULL, LED_1_PIN, LED_ON_LEVEL);
        }
        else
        {
            R_IOPORT_PinWrite(NULL, LED_1_PIN, LED_OFF_LEVEL);
        }
    }

    R_IOPORT_PinWrite(NULL, LED_2_PIN, LED_ON_LEVEL);

    while (1)
    {
        tx_thread_sleep (1);
    }
}

void netx_packet_pool_setup(void)
{
    /* TODO(5): Add packet pool setup code */
}

void netx_ip_setup(void)
{
    /* TODO(6): Add netx ip setup code */
}

void netx_dhcp_client_setup(void)
{
    /* TODO(7): Add dhcp setup code */
}

void netx_dns_setup(void)
{
    /* TODO(8): Add netx dns setup code */
}

void server_ip_address_get(void)
{
    /* TODO(9): Add ip address acquisition code */
}

void openai_inquiry_start(void)
{
    /* TODO(10): Add openai inquiry packet send code */
}

void irq_sw1_cb(external_irq_callback_args_t *p_args)
{
    FSP_PARAMETER_NOT_USED(p_args);
    g_sw1_pushed = true;
}
```

:::note info
e2studio IDE+FSPの開発環境は、 **Developer Assitance** と呼ぶコンフィグレータの設定内容に基づいた各種モジュールのセットアップコードを作成してくれる機能があります。プロジェクトフォルダの **Developer Assistance** でツリーを開いていくと、緑色の丸アイコンが現れ、Call ... setupと書かれた部分をドラッグ&ドロップするだけでコードを簡単に実装できます。 対象を選択し、**Properties** ビューでプレビューを見ることもできます。
以降のステップでは、青枠のハイライトで生成されたコードをベースとして実装していきます。
<img width="800" alt="ra-openai-2-1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/760a0832-a820-1c22-2575-5998cc53b4db.png">
:::

### 6. コード実装：ネットワークI/F初期化
#### 6.1 NetX Packet Poolセットアップ
適用場所：TODO(2)
```new_thread_entry.c
/* Global packet pool */
NX_PACKET_POOL g_packet_pool0;
uint8_t g_packet_pool0_pool_memory[G_PACKET_POOL0_PACKET_NUM * (G_PACKET_POOL0_PACKET_SIZE + sizeof(NX_PACKET))] BSP_ALIGN_VARIABLE(4) ETHER_BUFFER_PLACE_IN_SECTION;

```
適用場所：TODO(5)
```new_thread_entry.c
void netx_packet_pool_setup(void)
{
    UINT status = NX_SUCCESS;
    INFO_PRINT("NetX Packet Pool Setup:\n");

    /* Create the packet pool. */
    status = nx_packet_pool_create(&g_packet_pool0,
                                   "g_packet_pool0 Packet Pool",
                                   G_PACKET_POOL0_PACKET_SIZE,
                                   &g_packet_pool0_pool_memory[0],
                                   G_PACKET_POOL0_PACKET_NUM * (G_PACKET_POOL0_PACKET_SIZE + sizeof(NX_PACKET)));
    ERROR_ASSERT(status != NX_SUCCESS, "nx_packet_pool_create", status, __BKPT(0));

    INFO_PRINT("  Completed successfully\n\n");
}
```

#### 6.2 NetX IPセットアップ
適用場所：TODO(2)
```new_thread_entry.c
/* IP instance */
NX_IP g_ip0;

/* Stack memory for g_ip0. */
uint8_t g_ip0_stack_memory[G_IP0_TASK_STACK_SIZE] BSP_PLACE_IN_SECTION(".stack.g_ip0") BSP_ALIGN_VARIABLE(BSP_STACK_ALIGNMENT);

/* ARP cache memory for g_ip0. */
uint8_t g_ip0_arp_cache_memory[G_IP0_ARP_CACHE_SIZE] BSP_ALIGN_VARIABLE(4);
```
適用場所：TODO(6)
```new_thread_entry.c
void netx_ip_setup(void)
{
    UINT status;
    INFO_PRINT("NetX IP Setup:\n");

    /* Create the ip instance. */
    status = nx_ip_create(&g_ip0,
                          "g_ip0 IP Instance",
                          G_IP0_ADDRESS,
                          G_IP0_SUBNET_MASK,
                          &g_packet_pool0,
                          G_IP0_NETWORK_DRIVER,
                          &g_ip0_stack_memory[0],
                          G_IP0_TASK_STACK_SIZE,
                          G_IP0_TASK_PRIORITY);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_ip_create", status, __BKPT(0));

    /* If using Embedded Wireless Framework then uncomment this out to set the adapter_ptr. See the FSP user manual for more details. */
    // g_ip0.nx_ip_reserved_ptr = adapter_ptr;

    /* Set the gateway address if it is configured. Make sure this is set if using the Embedded Wireless Framework! */
    if(IP_ADDRESS(0, 0, 0, 0) != G_IP0_GATEWAY_ADDRESS)
    {
        status = nx_ip_gateway_address_set(&g_ip0, G_IP0_GATEWAY_ADDRESS);
        ERROR_ASSERT(status != NX_SUCCESS, "nx_ip_gateway_address_set", status, __BKPT(0));
    }

    status = nx_arp_enable(&g_ip0, &g_ip0_arp_cache_memory[0], G_IP0_ARP_CACHE_SIZE);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_arp_enable", status, __BKPT(0));

    /* Enable TCP */
    status = nx_tcp_enable(&g_ip0);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_tcp_enable", status, __BKPT(0));

    /* Enable UDP */
    status = nx_udp_enable(&g_ip0);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_udp_enable", status, __BKPT(0));

    /* Enable ICMP */
    status = nx_icmp_enable(&g_ip0);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_icmp_enable", status, __BKPT(0));

    INFO_PRINT("  Wait for IP link up\n");

    /* Wait for the link to be enabled. */
    ULONG current_state;
    do{
        nx_ip_status_check(&g_ip0, NX_IP_LINK_ENABLED, &current_state, 1000U);
    }while(NX_IP_LINK_ENABLED != current_state);

    INFO_PRINT("  Completed successfully\n\n");
}
```

### 7. コード実装：ネットワーク接続&サーバIPアドレス取得
#### 7.1 NetX DHCP Clientセットアップ&デバイスIPアドレス取得
適用場所：TODO(2)
```new_thread_entry.c
/* DHCP instance. */
NX_DHCP g_dhcp_client0;
```
適用場所：TODO(7)
```new_thread_entry.c
void netx_dhcp_client_setup(void)
{
    UINT status = NX_SUCCESS;
    INFO_PRINT("NetX DHCP Client Setup:\n");

    /* Create the DHCP instance. */
    status = nx_dhcp_create(&g_dhcp_client0,
                            &g_ip0,
                            "g_dhcp_client0");
    ERROR_ASSERT(status != NX_SUCCESS, "nx_dhcp_create", status, __BKPT(0));

    /* Set packet pool for DHCP */
    status = nx_dhcp_packet_pool_set(&g_dhcp_client0, &g_packet_pool0);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_dhcp_packet_pool_set", status, __BKPT(0));

    /* Start DHCP service. */
    status = nx_dhcp_start(&g_dhcp_client0);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_dhcp_start", status, __BKPT(0));

    /* Wait until an IP address is acquired via DHCP. */
    ULONG requested_status;
    status = nx_ip_status_check(&g_ip0, NX_IP_ADDRESS_RESOLVED, &requested_status, 1000);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_ip_status_check", status, __BKPT(0));
    ERROR_ASSERT(requested_status != NX_IP_ADDRESS_RESOLVED, "nx_ip_status_check", requested_status, __BKPT(0));

    /* Get the updated ip address */
    ULONG ip_address;
    ULONG network_mask;
    status = nx_ip_interface_address_get(&g_ip0, 0, &ip_address, &network_mask);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_ip_interface_address_get", status, __BKPT(0));

    INFO_PRINT("\n");
    INFO_PRINT("  Device IP Address: %d:%d:%d:%d\n", (ip_address >> 24) & 0xFF,
                                                     (ip_address >> 16) & 0xFF,
                                                     (ip_address >> 8) & 0xFF,
                                                     (ip_address >> 0) & 0xFF);
    INFO_PRINT("\n");

    INFO_PRINT("  Completed successfully\n\n");
}
```

#### 7.2 DNSセットアップ
適用場所：TODO(2)
```new_thread_entry.c
/* DNS instance. */
NX_DNS g_dns0;

#define DNS_SERVER_ADDRESS IP_ADDRESS(8,8,8,8)
```
適用場所：TODO(8)
```new_thread_entry.c
void netx_dns_setup(void)
{
    UINT status = NX_SUCCESS;
    INFO_PRINT("NetX DNS Setup:\n");

    /* Create the DNS instance. */
    status = nx_dns_create(&g_dns0,
                           &g_ip0,
                           (UCHAR *)"g_dns0");
    ERROR_ASSERT(status != NX_SUCCESS, "nx_dns_create", status, __BKPT(0));

    /* Set packet pool for DNS */
    status = nx_dns_packet_pool_set(&g_dns0, &g_packet_pool0);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_dns_packet_pool_set", status, __BKPT(0));

    INFO_PRINT("  Completed successfully\n\n");
}
```

#### 7.3 サーバIPアドレス取得
適用場所：TODO(2)
```new_thread_entry.c
NXD_ADDRESS server_ip_address;
#define HTTP_HOST_NAME                  "api.openai.com"
const UCHAR http_host_name_str[] =      HTTP_HOST_NAME;
#define HTTP_HOST_IP_ADDRESS_EXAMPLE    IP_ADDRESS(104,18,6,192)
```
適用場所：TODO(9)
```new_thread_entry.c
void server_ip_address_get(void)
{
    UINT status = NX_SUCCESS;
    ULONG record_buffer[50];
    UINT  record_count;

    INFO_PRINT("Server IP Address Get:\n");

    INFO_PRINT("\n");
    INFO_PRINT("  Server Host Name: %s\n", HTTP_HOST_NAME);

    /* Add an IPv4 server address to the Client list. */
    status = nx_dns_server_add(&g_dns0, DNS_SERVER_ADDRESS);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_dns_server_add", status, __BKPT(0));

    /* Request the IPv4 address for the specified host. */
    status =  nx_dns_ipv4_address_by_name_get(&g_dns0,
                                              (UCHAR *)HTTP_HOST_NAME,
                                              record_buffer,
                                              sizeof(record_buffer),&record_count,
                                              500);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_dns_ipv4_address_by_name_get", status, __BKPT(0));

    if(record_count > 0)
    {
        server_ip_address.nxd_ip_address.v4 = record_buffer[0];
        server_ip_address.nxd_ip_version = NX_IP_VERSION_V4;

        INFO_PRINT("  Server IP Address: %d:%d:%d:%d\n", (server_ip_address.nxd_ip_address.v4 >> 24) & 0xFF,
                                                         (server_ip_address.nxd_ip_address.v4 >> 16) & 0xFF,
                                                         (server_ip_address.nxd_ip_address.v4 >> 8) & 0xFF,
                                                         (server_ip_address.nxd_ip_address.v4 >> 0) & 0xFF);
    }
    else
    {
        server_ip_address.nxd_ip_address.v4 = HTTP_HOST_IP_ADDRESS_EXAMPLE;
        server_ip_address.nxd_ip_version = NX_IP_VERSION_V4;

        INFO_PRINT("  No record found\n\n");
        return;
    }
    INFO_PRINT("\n");

    INFO_PRINT("  Completed successfully\n\n");
}
```

### 8. コード実装：通知送信
#### 8.1 メッセージデータ生成
適用場所：TODO(2)
```new_thread_entry.c
/// ########## User Settings ##########
#define ACCESS_AUTH_API_KEY             "<add your access auth number>"

char    openai_api_model_str[20] =      "gpt-3.5-turbo";
// "text-davinci-003" for text completions, "gpt-3.5-turbo" for chat
// See https://platform.openai.com/docs/models/overview for detail
char    openai_api_temperature_str[5] = "0";
char    openai_api_max_tokens_str[5] =  "200";

#define OPENSI_API_MESSAGE_STYLE        (1) //0: Text completion, 1: Chat
/// ###################################

// HTTP Request Parameters:
#if (OPENSI_API_MESSAGE_STYLE == 0)
#define HTTP_HOST_RESOURCE              "/v1/completions"
#else
#define HTTP_HOST_RESOURCE              "/v1/chat/completions"
#endif

#define POST_MESSAGE_SAMPLE             "Hello OpenAI! Say this is a test"

#if (OPENSI_API_MESSAGE_STYLE == 0)
#define OPENAI_API_RECV_MESSAGE_FIELD_PRE "\"text\":\""
#else
#define OPENAI_API_RECV_MESSAGE_FIELD_PRE "\"content\":\""
#endif

#if (OPENSI_API_MESSAGE_STYLE == 0)
#define OPENAI_API_SEND_MESSAGE_FIELD   "\"prompt\":\"%s\""
#else
#define OPENAI_API_SEND_MESSAGE_FIELD   "\"messages\":[{\"role\": \"user\", \"content\":\"%s\"}]"
#endif

unsigned char post_message[151];
unsigned char transfer_message_buff[700];
unsigned long transfer_message_len;
```
適用場所：TODO(4-1)
```new_thread_entry.c
            memset(post_message, 0x0, sizeof(post_message));
            sprintf((char*)post_message, POST_MESSAGE_SAMPLE"#%d.", sw_push_counter);
            INFO_PRINT("You> %s\n", post_message);

            transfer_message_len = sprintf((char*)transfer_message_buff,
                                           "{\"model\":\"%s\","OPENAI_API_SEND_MESSAGE_FIELD",\"temperature\":%s, \"max_tokens\":%s}",
                                           openai_api_model_str, post_message, openai_api_temperature_str, openai_api_max_tokens_str);
```

適用場所：TODO(4-2)
```new_thread_entry.c
        if(SEGGER_RTT_HasKey())
        {
```

適用場所：TODO(4-3)
```new_thread_entry.c
            memset(post_message, 0x0, sizeof(post_message));
            unsigned char data;
            UINT post_message_buff_count = 0x0;
            do
            {
                SEGGER_RTT_Read (0, &data, 1);
                post_message[post_message_buff_count] = data;
                post_message_buff_count++;
                ERROR_ASSERT(post_message_buff_count > sizeof(post_message), "RTT data read. Input data size is invalid.", 0x0, __BKPT(0));
            }while(data != 0x0a && data != 0x00);
            
            post_message[post_message_buff_count - 1] = 0x0; // Remove LF code
            INFO_PRINT("You> %s\n", post_message);

            /* Prepare message data */
            transfer_message_len = sprintf((char*)transfer_message_buff,
                                           "{\"model\":\"%s\","OPENAI_API_SEND_MESSAGE_FIELD",\"temperature\":%s, \"max_tokens\":%s}",
                                           openai_api_model_str, post_message, openai_api_temperature_str, openai_api_max_tokens_str);
```

#### 8.2 メッセージデータをHTTPsで送信
適用場所：TODO(2)
```new_thread_entry.c
#define HTTP_AUTH_FIELD_NAME            "Authorization"
#define HTTP_AUTH_FIELD_VALUE           "Bearer "
#define HTTP_CONTENTTYPE_FIELD_NAME     "Content-Type"
#define HTTP_CONTENTTYPE_FIELD_VALUE    "application/json"

/* Web HTTP Client instance. */
NX_WEB_HTTP_CLIENT  g_web_http_client0;

unsigned char receive_message_buff[101];
unsigned long receive_message_len;
```

適用場所：TODO(3)
```new_thread_entry.c
VOID http_response_callback(NX_WEB_HTTP_CLIENT *client_ptr, CHAR *field_name, UINT field_name_length,
                            CHAR *field_value, UINT field_value_length);

UINT tls_setup_callback(NX_WEB_HTTP_CLIENT *client_ptr, NX_SECURE_TLS_SESSION *tls_session);
```

適用場所：TODO(10)
```new_thread_entry.c
void openai_inquiry_start(void)
{
    UINT status = NX_SUCCESS;
    UINT status2 = NX_SUCCESS;
    NX_PACKET *my_packet;
    NX_PACKET *receive_packet;
    UINT retry_count = 0x0;
    UINT receive_status = 0x0; // 0x0: Not message body part, 0x1: Reading message body, 0x2: Completed
    UINT receive_message_total_size = 0x0;

    /* Create the Web HTTP Client instance. */
    status = nx_web_http_client_create(&g_web_http_client0,
                                       "g_web_http_client0",
                                       &g_ip0,
                                       &g_packet_pool0,
                                       G_WEB_HTTP_CLIENT0_WINDOW_SIZE);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_web_http_client_create", status, NULL);

    /* Set the header callback routine. */
    if(status == NX_SUCCESS)
    {
        status = nx_web_http_client_response_header_callback_set(&g_web_http_client0, http_response_callback);
        ERROR_ASSERT(status != NX_SUCCESS, "nx_web_http_client_response_header_callback_set", status, NULL);
    }

    /* Make secure connect to server */
    if(status == NX_SUCCESS)
    {
        status = nx_web_http_client_secure_connect(&g_web_http_client0,
                                                   &server_ip_address, NX_WEB_HTTPS_SERVER_PORT,
                                                   tls_setup_callback, NX_WAIT_FOREVER);
        ERROR_ASSERT(status != NX_SUCCESS, "nx_web_http_client_secure_connect", status, NULL);
    }

    /* Initialize request packet */
    if(status == NX_SUCCESS)
    {
        status = nx_web_http_client_request_initialize_extended(&g_web_http_client0,
                                                                NX_WEB_HTTP_METHOD_POST, /* GET, PUT, DELETE, POST, HEAD */
                                                                HTTP_HOST_RESOURCE, sizeof(HTTP_HOST_RESOURCE) - 1,
                                                                HTTP_HOST_NAME, sizeof(HTTP_HOST_NAME) - 1,
                                                                transfer_message_len,
                                                                NX_FALSE,
                                                                NULL, 0, //"name",
                                                                NULL, 0, // "password",
                                                                NX_WAIT_FOREVER);
        ERROR_ASSERT(status != NX_SUCCESS, "nx_web_http_client_request_initialize_extended", status, NULL);
    }

    /* Add additional http header */
    if(status == NX_SUCCESS)
    {
        status = nx_web_http_client_request_header_add(&g_web_http_client0,
                                                       HTTP_AUTH_FIELD_NAME, sizeof(HTTP_AUTH_FIELD_NAME) - 1,
                                                       HTTP_AUTH_FIELD_VALUE ACCESS_AUTH_API_KEY, sizeof(HTTP_AUTH_FIELD_VALUE ACCESS_AUTH_API_KEY) - 1,
                                                       NX_WAIT_FOREVER);
        ERROR_ASSERT(status != NX_SUCCESS, "nx_web_http_client_request_header_add (AUTH)", status, NULL);
    }

    /* Add additional http header */
    if(status == NX_SUCCESS)
    {
        status = nx_web_http_client_request_header_add(&g_web_http_client0,
                                                       HTTP_CONTENTTYPE_FIELD_NAME, sizeof(HTTP_CONTENTTYPE_FIELD_NAME) - 1,
                                                       HTTP_CONTENTTYPE_FIELD_VALUE, sizeof(HTTP_CONTENTTYPE_FIELD_VALUE) - 1,
                                                       NX_WAIT_FOREVER);
        ERROR_ASSERT(status != NX_SUCCESS, "nx_web_http_client_request_header_add (CONTENT TYPE)", status, NULL);
    }

    /* Start sending http packet request to server */
    if(status == NX_SUCCESS)
    {
        status = nx_web_http_client_request_send(&g_web_http_client0, NX_WAIT_FOREVER);
        ERROR_ASSERT(status != NX_SUCCESS, "nx_web_http_client_request_send", status, NULL);
    }

    /* Create a new data packet request */
    if(status == NX_SUCCESS)
    {
        status = nx_web_http_client_request_packet_allocate(&g_web_http_client0,
                                                            &my_packet,
                                                            NX_WAIT_FOREVER);
        ERROR_ASSERT(status != NX_SUCCESS, "nx_web_http_client_request_packet_allocate", status, NULL);
    }

    /* Fill the data into new data packet */
    if(status == NX_SUCCESS)
    {
        status = nx_packet_data_append(my_packet,
                                       transfer_message_buff, transfer_message_len,
                                       &g_packet_pool0,
                                       NX_WAIT_FOREVER);
        ERROR_ASSERT(status != NX_SUCCESS, "nx_packet_data_append", status, NULL);
    }

    /* Send data packet request to server. */
    if(status == NX_SUCCESS)
    {
        status = nx_web_http_client_request_packet_send(&g_web_http_client0,
                                                        my_packet,
                                                        0,
                                                        NX_WAIT_FOREVER);
        ERROR_ASSERT(status != NX_SUCCESS, "nx_web_http_client_request_packet_send", status, NULL);
    }

    /* Check response */
    if(status == NX_SUCCESS)
    {
        UINT get_status = NX_SUCCESS;
        while(get_status != NX_WEB_HTTP_GET_DONE)
        {
            /* Check and get response body */
            get_status = nx_web_http_client_response_body_get(&g_web_http_client0,
                                                              &receive_packet,
                                                              10000);

            if (get_status != NX_SUCCESS && get_status != NX_WEB_HTTP_GET_DONE)
            {
                status = get_status;

                if(retry_count > 4)
                {
                    ERROR_ASSERT(1, "nx_web_http_client_response_body_get", get_status, break);
                }
                else
                {
                    retry_count++;
                    INFO_PRINT("  Response body receiving again (#%d) \n", retry_count);
                    tx_thread_sleep (100);
                }
            }
            else
            {
                receive_message_total_size = 0x0;
                while(receive_status < 0x2)
                {
                    /* Clear buffer */
                    memset(receive_message_buff, 0x0, sizeof(receive_message_buff));

                    /* Receive data packet from packet pool */
                    status = nx_packet_data_extract_offset(receive_packet, receive_message_total_size, receive_message_buff, sizeof(receive_message_buff) - 1, &receive_message_len);
                    if(status != NX_SUCCESS)
                    {
                        if(receive_status == 0x0)
                        {
                            ERROR_ASSERT(1, "extracting response body data", status, break);
                        }
                        break;
                    }
                    else
                    {
                        if(receive_message_len > 0)
                        {
                            if(receive_status == 0x0)
                            {
                                char *p_text_s = strstr((const char *)receive_message_buff, OPENAI_API_RECV_MESSAGE_FIELD_PRE);
                                if(p_text_s != NULL)
                                {
                                    p_text_s += sizeof(OPENAI_API_RECV_MESSAGE_FIELD_PRE) - 1;
                                    if(*(p_text_s + 0) == '\\' && *(p_text_s + 1) == 'n' && *(p_text_s + 2) == '\\' && *(p_text_s + 3) == 'n')
                                    {
                                        p_text_s += 4;
                                    }

                                    receive_message_total_size += (UINT)(p_text_s - (char*)&receive_message_buff[0]);
                                    receive_status = 0x1;

                                    INFO_PRINT("AI>  ");
                                }
                                else
                                {
                                    receive_message_total_size += receive_message_len;
                                }
                            }
                            else
                            {
                                char *p_text_e = strstr((const char *)receive_message_buff, "\"");
                                if(p_text_e != NULL)
                                {
                                    if(*(p_text_e - 1) == '\\' && *(p_text_e + 0) == '\"')
                                    {
                                        /* Do nothing. Skip this catch. */
                                    }
                                    else
                                    {
                                        *p_text_e = 0x0;
                                        receive_status = 0x2;
                                    }
                                }

                                INFO_PRINT("%s\n     ", &receive_message_buff);

                                receive_message_total_size += receive_message_len;
                            }
#if 0
                            INFO_PRINT("\n  Received packet data: %s\n", receive_message_buff);
#endif
                        }
                    }
                }
            }
            /* Release current packet */
            nx_packet_release(receive_packet);
        }
    }

    /* Clear out the HTTP client */
    status2 = nx_web_http_client_delete(&g_web_http_client0);
    ERROR_ASSERT(status2 != NX_SUCCESS, "nx_web_http_client_delete", status2, NULL);

    INFO_PRINT("\n\n");

    if(status != NX_SUCCESS)
    {
        tx_thread_sleep (100);
        R_IOPORT_PinWrite(NULL, LED_2_PIN, LED_OFF_LEVEL);
    }
}
```

適用場所：ファイル末尾
```new_thread_entry.c
VOID http_response_callback(NX_WEB_HTTP_CLIENT *client_ptr, CHAR *field_name, UINT field_name_length,
                            CHAR *field_value, UINT field_value_length)
{
    CHAR name[100];
    CHAR value[100];

    NX_PARAMETER_NOT_USED(client_ptr);

    memset(name, 0, sizeof(name));
    memset(value, 0, sizeof(value));

    memcpy(name, field_name, field_name_length);
    memcpy(value, field_value, field_value_length);

#if 0
    INFO_PRINT("  Received header: \n\tField name: %s (%d bytes)\n\tValue: %s (%d bytes)\n--------------\n",
               name, field_name_length,
               value, field_value_length);
#endif
}
```

**TLS関連コード**
適用場所：TODO(2)
```new_thread_entry.c
unsigned char http_server_ca_cert_der[];
unsigned int http_server_ca_cert_der_len;

CHAR                crypto_metadata_client[20000 * NX_WEB_HTTP_SERVER_SESSION_MAX];
UCHAR               tls_packet_buffer[40000];
NX_SECURE_X509_CERT trusted_certificate;
NX_SECURE_X509_CERT remote_certificate, remote_issuer;
UCHAR               remote_cert_buffer[2000];
UCHAR               remote_issuer_buffer[2000];

extern const NX_SECURE_TLS_CRYPTO nx_crypto_tls_ciphers;

NX_SECURE_X509_DNS_NAME dns_name;
```

適用場所：ファイル末尾
```new_thread_entry.c
UINT tls_setup_callback(NX_WEB_HTTP_CLIENT *client_ptr, NX_SECURE_TLS_SESSION *tls_session)
{
    UINT status;
    NX_PARAMETER_NOT_USED(client_ptr);

    /* Initialize and create TLS session. */
    status = nx_secure_tls_session_create(tls_session,
                                          &nx_crypto_tls_ciphers,
                                          crypto_metadata_client,
                                          sizeof(crypto_metadata_client));
    ERROR_ASSERT(status != NX_SUCCESS, "nx_secure_tls_session_create", status, return(status));

    /* Initialize the server DNS name. */
    status = nx_secure_x509_dns_name_initialize(&dns_name, &http_host_name_str[0],
                                                sizeof(http_host_name_str) - 1);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_secure_x509_dns_name_initialize", status, return(status));

    /* Initialize SNI extension in previously-initialized TLS Session. */
    status = nx_secure_tls_session_sni_extension_set(tls_session, &dns_name);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_secure_tls_session_sni_extension_set", status, return(status));

    /* Allocate space for packet reassembly. */
    status = nx_secure_tls_session_packet_buffer_set(tls_session,
                                                     tls_packet_buffer,
                                                     sizeof(tls_packet_buffer));
    ERROR_ASSERT(status != NX_SUCCESS, "nx_secure_tls_session_packet_buffer_set", status, return(status));

    /* Add a CA Certificate to our trusted store for verifying incoming server certificates. */
    status = nx_secure_x509_certificate_initialize(&trusted_certificate,
                                                   http_server_ca_cert_der, (USHORT)http_server_ca_cert_der_len,
                                                   NX_NULL, 0,
                                                   NULL, 0,
                                                   NX_SECURE_X509_KEY_TYPE_NONE);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_secure_x509_certificate_initialize", status, return(status));

    /* Apply trusted certificate to TLS instance */
    status = nx_secure_tls_trusted_certificate_add(tls_session, &trusted_certificate);
    ERROR_ASSERT(status != NX_SUCCESS, "nx_secure_tls_trusted_certificate_add", status, return(status));

        /* Need to allocate space for the certificate coming in from the remote host. */
    nx_secure_tls_remote_certificate_allocate(tls_session, &remote_certificate, remote_cert_buffer, sizeof(remote_cert_buffer));
    nx_secure_tls_remote_certificate_allocate(tls_session, &remote_issuer, remote_issuer_buffer, sizeof(remote_issuer_buffer));

    return(NX_SUCCESS);
}
```

TLS接続を行うためには、OpenAIのサーバ(api.openai.com)が使用するルートCA証明書をインストールする必要があります。
またNetX Secureミドルウェアが認識できる証明書形式はDER形式になります。DER形式かつC言語コードに変換し、プログラムに適用します。
執筆時点でのOpenAIのサーバ(api.openai.com)が使用しているCA（Baltimore CyberTrust Root（有効期間：2000/5/13~2025/5/13））のCコードは、以下の通りです。

適用場所：ファイル末尾
```new_thread_entry.c
unsigned char http_server_ca_cert_der[] = {
  0x30, 0x82, 0x03, 0x77, 0x30, 0x82, 0x02, 0x5F, 0xA0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x04, 0x02, 0x00, 0x00, 0xB9, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x5A, 0x31, 0x0B, 0x30, 0x09,
  0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x09, 0x42, 0x61, 0x6C, 0x74, 0x69, 0x6D, 0x6F, 0x72, 0x65, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x13, 0x0A,
  0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6C, 0x74, 0x69, 0x6D, 0x6F, 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73,
  0x74, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x30, 0x1E, 0x17, 0x0D, 0x30, 0x30, 0x30, 0x35, 0x31, 0x32, 0x31, 0x38, 0x34, 0x36, 0x30, 0x30, 0x5A, 0x17, 0x0D, 0x32, 0x35, 0x30, 0x35, 0x31, 0x32, 0x32, 0x33, 0x35, 0x39, 0x30, 0x30, 0x5A, 0x30, 0x5A,
  0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x49, 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x09, 0x42, 0x61, 0x6C, 0x74, 0x69, 0x6D, 0x6F, 0x72, 0x65, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55,
  0x04, 0x0B, 0x13, 0x0A, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6C, 0x74, 0x69, 0x6D, 0x6F, 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72,
  0x54, 0x72, 0x75, 0x73, 0x74, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0F, 0x00, 0x30, 0x82, 0x01, 0x0A, 0x02, 0x82,
  0x01, 0x01, 0x00, 0xA3, 0x04, 0xBB, 0x22, 0xAB, 0x98, 0x3D, 0x57, 0xE8, 0x26, 0x72, 0x9A, 0xB5, 0x79, 0xD4, 0x29, 0xE2, 0xE1, 0xE8, 0x95, 0x80, 0xB1, 0xB0, 0xE3, 0x5B, 0x8E, 0x2B, 0x29, 0x9A, 0x64, 0xDF, 0xA1, 0x5D, 0xED, 0xB0, 0x09, 0x05,
  0x6D, 0xDB, 0x28, 0x2E, 0xCE, 0x62, 0xA2, 0x62, 0xFE, 0xB4, 0x88, 0xDA, 0x12, 0xEB, 0x38, 0xEB, 0x21, 0x9D, 0xC0, 0x41, 0x2B, 0x01, 0x52, 0x7B, 0x88, 0x77, 0xD3, 0x1C, 0x8F, 0xC7, 0xBA, 0xB9, 0x88, 0xB5, 0x6A, 0x09, 0xE7, 0x73, 0xE8, 0x11,
  0x40, 0xA7, 0xD1, 0xCC, 0xCA, 0x62, 0x8D, 0x2D, 0xE5, 0x8F, 0x0B, 0xA6, 0x50, 0xD2, 0xA8, 0x50, 0xC3, 0x28, 0xEA, 0xF5, 0xAB, 0x25, 0x87, 0x8A, 0x9A, 0x96, 0x1C, 0xA9, 0x67, 0xB8, 0x3F, 0x0C, 0xD5, 0xF7, 0xF9, 0x52, 0x13, 0x2F, 0xC2, 0x1B,
  0xD5, 0x70, 0x70, 0xF0, 0x8F, 0xC0, 0x12, 0xCA, 0x06, 0xCB, 0x9A, 0xE1, 0xD9, 0xCA, 0x33, 0x7A, 0x77, 0xD6, 0xF8, 0xEC, 0xB9, 0xF1, 0x68, 0x44, 0x42, 0x48, 0x13, 0xD2, 0xC0, 0xC2, 0xA4, 0xAE, 0x5E, 0x60, 0xFE, 0xB6, 0xA6, 0x05, 0xFC, 0xB4,
  0xDD, 0x07, 0x59, 0x02, 0xD4, 0x59, 0x18, 0x98, 0x63, 0xF5, 0xA5, 0x63, 0xE0, 0x90, 0x0C, 0x7D, 0x5D, 0xB2, 0x06, 0x7A, 0xF3, 0x85, 0xEA, 0xEB, 0xD4, 0x03, 0xAE, 0x5E, 0x84, 0x3E, 0x5F, 0xFF, 0x15, 0xED, 0x69, 0xBC, 0xF9, 0x39, 0x36, 0x72,
  0x75, 0xCF, 0x77, 0x52, 0x4D, 0xF3, 0xC9, 0x90, 0x2C, 0xB9, 0x3D, 0xE5, 0xC9, 0x23, 0x53, 0x3F, 0x1F, 0x24, 0x98, 0x21, 0x5C, 0x07, 0x99, 0x29, 0xBD, 0xC6, 0x3A, 0xEC, 0xE7, 0x6E, 0x86, 0x3A, 0x6B, 0x97, 0x74, 0x63, 0x33, 0xBD, 0x68, 0x18,
  0x31, 0xF0, 0x78, 0x8D, 0x76, 0xBF, 0xFC, 0x9E, 0x8E, 0x5D, 0x2A, 0x86, 0xA7, 0x4D, 0x90, 0xDC, 0x27, 0x1A, 0x39, 0x02, 0x03, 0x01, 0x00, 0x01, 0xA3, 0x45, 0x30, 0x43, 0x30, 0x1D, 0x06, 0x03, 0x55, 0x1D, 0x0E, 0x04, 0x16, 0x04, 0x14, 0xE5,
  0x9D, 0x59, 0x30, 0x82, 0x47, 0x58, 0xCC, 0xAC, 0xFA, 0x08, 0x54, 0x36, 0x86, 0x7B, 0x3A, 0xB5, 0x04, 0x4D, 0xF0, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1D, 0x13, 0x01, 0x01, 0xFF, 0x04, 0x08, 0x30, 0x06, 0x01, 0x01, 0xFF, 0x02, 0x01, 0x03, 0x30,
  0x0E, 0x06, 0x03, 0x55, 0x1D, 0x0F, 0x01, 0x01, 0xFF, 0x04, 0x04, 0x03, 0x02, 0x01, 0x06, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x85, 0x0C, 0x5D, 0x8E, 0xE4,
  0x6F, 0x51, 0x68, 0x42, 0x05, 0xA0, 0xDD, 0xBB, 0x4F, 0x27, 0x25, 0x84, 0x03, 0xBD, 0xF7, 0x64, 0xFD, 0x2D, 0xD7, 0x30, 0xE3, 0xA4, 0x10, 0x17, 0xEB, 0xDA, 0x29, 0x29, 0xB6, 0x79, 0x3F, 0x76, 0xF6, 0x19, 0x13, 0x23, 0xB8, 0x10, 0x0A, 0xF9,
  0x58, 0xA4, 0xD4, 0x61, 0x70, 0xBD, 0x04, 0x61, 0x6A, 0x12, 0x8A, 0x17, 0xD5, 0x0A, 0xBD, 0xC5, 0xBC, 0x30, 0x7C, 0xD6, 0xE9, 0x0C, 0x25, 0x8D, 0x86, 0x40, 0x4F, 0xEC, 0xCC, 0xA3, 0x7E, 0x38, 0xC6, 0x37, 0x11, 0x4F, 0xED, 0xDD, 0x68, 0x31,
  0x8E, 0x4C, 0xD2, 0xB3, 0x01, 0x74, 0xEE, 0xBE, 0x75, 0x5E, 0x07, 0x48, 0x1A, 0x7F, 0x70, 0xFF, 0x16, 0x5C, 0x84, 0xC0, 0x79, 0x85, 0xB8, 0x05, 0xFD, 0x7F, 0xBE, 0x65, 0x11, 0xA3, 0x0F, 0xC0, 0x02, 0xB4, 0xF8, 0x52, 0x37, 0x39, 0x04, 0xD5,
  0xA9, 0x31, 0x7A, 0x18, 0xBF, 0xA0, 0x2A, 0xF4, 0x12, 0x99, 0xF7, 0xA3, 0x45, 0x82, 0xE3, 0x3C, 0x5E, 0xF5, 0x9D, 0x9E, 0xB5, 0xC8, 0x9E, 0x7C, 0x2E, 0xC8, 0xA4, 0x9E, 0x4E, 0x08, 0x14, 0x4B, 0x6D, 0xFD, 0x70, 0x6D, 0x6B, 0x1A, 0x63, 0xBD,
  0x64, 0xE6, 0x1F, 0xB7, 0xCE, 0xF0, 0xF2, 0x9F, 0x2E, 0xBB, 0x1B, 0xB7, 0xF2, 0x50, 0x88, 0x73, 0x92, 0xC2, 0xE2, 0xE3, 0x16, 0x8D, 0x9A, 0x32, 0x02, 0xAB, 0x8E, 0x18, 0xDD, 0xE9, 0x10, 0x11, 0xEE, 0x7E, 0x35, 0xAB, 0x90, 0xAF, 0x3E, 0x30,
  0x94, 0x7A, 0xD0, 0x33, 0x3D, 0xA7, 0x65, 0x0F, 0xF5, 0xFC, 0x8E, 0x9E, 0x62, 0xCF, 0x47, 0x44, 0x2C, 0x01, 0x5D, 0xBB, 0x1D, 0xB5, 0x32, 0xD2, 0x47, 0xD2, 0x38, 0x2E, 0xD0, 0xFE, 0x81, 0xDC, 0x32, 0x6A, 0x1E, 0xB5, 0xEE, 0x3C, 0xD5, 0xFC,
  0xE7, 0x81, 0x1D, 0x19, 0xC3, 0x24, 0x42, 0xEA, 0x63, 0x39, 0xA9
};

unsigned int http_server_ca_cert_der_len = sizeof(http_server_ca_cert_der);
```

### 9. Segger RTT Viewer デバッグ出力
適用場所：TODO(1)
```new_thread_entry.c
#include "SEGGER_RTT/RTT/SEGGER_RTT.h"
#define printf(fn_, ...)         SEGGER_RTT_printf(0, (fn_), ##__VA_ARGS__)
```

デバッグ出力が必要ない場合、以下のようにしておくと良いと思います。
```new_thread_entry.c
#define printf(fn_, ...)
```

RTT Viewer用のソースファイルを追加します。J-Link Software and Document PackをSeger社のウェブサイト( [URL](https://www.segger.com/downloads/jlink/) )から入手し、インストールします。
<インストールフォルダ>\JLink\Samples\RTT\SEGGER_RTT_Vxxx.zip下の4つのコードをe2studioのプロジェクトフォルダにコピーします。フォルダ構成も以下のようにすることをお勧めします。
<img width="600" alt="ra-openai-2-2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/33665bd6-258b-118d-8074-98c907fc3c17.png">

:::note warn
SEGGER_RTT_printf.cファイル内56行目を以下に変更してください。
```SEGGER_RTT_printf.c
#include "../Config/SEGGER_RTT_Conf.h"
```
SEGGER_RTT_Conf.hファイルの99行目を以下に変更してください。
```SEGGER_RTT_Conf.h
  #define BUFFER_SIZE_DOWN                          (150)
```
:::

### 10. OpenAIから取得したAccess Auth番号を適用
OpenAIのAPI設定画面から取得した、アクセストークンをコードに適用します。
マクロ **ACCESS_AUTH_API_KEY** の値を変更してください。

### 11. ビルド
プロジェクトが選択された状態で、ハンマーボタン🔨をクリックし、ビルドします。
0 errorsと表示されていれば成功です。
<img width="600" alt="ra-openai-2-3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/dd5a3831-01b7-01dc-0b33-4565f5172aa2.png">

# 動作確認
評価ボードとPCをUSBケーブルで接続します。
プロジェクトがLaunch Configurationで選択されていることを確認し、虫ボタンを押します。
<img width="600" alt="ra-openai-2-4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/aa93be25-5428-5b25-7883-e21817a3cb2e.png">

:::note warn
Arm TrustZoneを搭載したRA MCUで動作確認をする場合、TrustZoneのメモリ境界を設定するために、デバッグ構成似て以下の項目が有効になっている必要があります。またデバッガとMCUとの間にもデバッグI/Fに加えて、シリアルI/Fを接続する必要があります。自作ボードでは注意ください。ルネサス製の評価ボードを使用する場合は、購入時のまま使えます。デバッグ構成で、境域設定が有効になっていることのみ確認ください。
<img width="500" alt="ra-openai-2-5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/cfb1c00e-27ba-ed7a-b1d1-13e084d85dc7.png">
:::

イメージのダウンロードが完了すると、Reset Handlerの先頭で停止します。
Resume▶ボタンを押し、プログラムを開始します。

Segger RTT Viewerを起動、以下のように設定し、OKを押します。
<img width="500" alt="ra-openai-2-6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/c89c9485-d97d-ade4-eb1e-e2911faee405.png">

接続が完了したら、Inpout設定を以下のように変更します。
<img width="400" alt="ra-openai-2-7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/2982ede2-0be3-c7cb-88c5-035ae02df994.png">
<img width="400" alt="ra-openai-2-7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/83e2b001-2758-78f5-d639-0c18520f0d73.png">

:::note info
e2studioのデバッグセッションが切断した状態で使用する場合は、USB Connectionを選択し、お使いのデバイスに合わせて **Tartget Device** を変更します。
:::

RTT Viewer接続が成功すると、以下のようなメッセージが表示されます。デバイスはLANケーブル接続によるNetwork Link Upを待っています。
<img width="600" alt="ra-openai-2-7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/9935e0e6-b223-b5f7-acab-7b590aa56433.png">

LANケーブルを開発ボードに接続するとネットワーク接続やサーバのIPアドレス取得など諸々の処理が実行されます。エラーが出ないことを祈ります。
正常にセットアップ完了したら緑色LED点滅、エラーが発生したら赤色LED点灯します。
<img width="600" alt="ra-openai-2-8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/f92d7895-9878-c1fa-6fa4-8fba133beaa9.png">

ここまできたら、あとは評価ボードのユーザスイッチS1を押すかRTT Viewer上でテキストを入力するだけです。

まずはユーザスイッチ（SW1）を押してみてください。HTTPs通信を使ってOpenAIサーバに対してメッセージ"Hello OpenAI! Say this is a test #1."を送信します。少し待つと以下のようにレスポンスが返ってきます!!
※勿論相手はAIですので状態によって返す言葉が変わります。
<img width="600" alt="ra-openai-2-9.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/e1696114-3e3f-554e-a1d5-79ab62f87254.png">

次に、自由テキストを入力してみます。
RTT Viewerのテキストウィンドウにテキストを入力し（①）、EnterキーまたはEnterボタンを押します（②）。メッセージがMCUに転送され（③）、OpenAIサーバに送信されます。少し待つとメッセージが返ってきます（④）。
例1：Hellow, OpenAI. How are you?
<img width="800" alt="ra-openai-2-10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/ee05b2b2-ee61-197c-8b60-d2e404b11527.png">

例2：What is Renesas RA Family MCU?
<img width="800" alt="ra-openai-2-11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/bd4047f0-86ba-709e-9c57-77ecb77623a0.png">

例3：Please correct. > I has a pens.
<img width="800" alt="ra-openai-2-12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199130/da7f604d-7de2-924f-b6b4-13733fdf2912.png">

あとは自由にテキストを入力してOpenAI ChatGPT AIの反応を楽しんでみてください！！

:::note info
nx_web_http_client_response_body_get()がエラー（0x300xx）で返った場合、以下のマクロ定義から原因を特定できる可能性があります。参考まで。
https://github.com/azure-rtos/netxduo/blob/master/addons/web/nx_web_http_common.h#L118

またはOpenAIのサーバは混み合っているようでたまに不明なエラーで失敗します。プログラムをリセットして再度実行すると成功する場合があります。
:::

:::note info
デモアプリケーションの制限事項
- 英語・テキストのみ可
- RTT Viewerからの自由入力可能文字数は150文字(終端LF含)です。
- チャットと言いながら、現在のアプリケーションは過去の会話履歴を活用できません。過去経緯を含めて会話したい場合はOpenAI APIメッセージボディに履歴を追加する必要があります。
- OpenAI APIのmax_tokensパラメータ（任意）はデフォルトで200。openai_api_max_tokens_strで変更可。
- OpenAI APIのtemperatureパラメータ（任意）はデフォルトで0。openai_api_temperature_strで変更可。
:::

:::note warn
契約プランにもよりますが、送信するメッセージやレスポンスの単語数に依存するトークン数によって、費用が発生する場合があります。OpenAIのAPI Referenceなどで十分に確認を取ってからご使用ください。
:::

# まとめ
以上、マイコン（RA MCU）を使ってOpenAIのChatGPT AIと会話するアプリケーション開発を紹介しましたが、いかがでしたでしょうか？

最先端のOpenAI AI技術を末端のマイコンで活用できることが分かったと思えます。
UnexplainableなAI技術は取り扱いに気を付ける必要がありますが、用途によっては活用することで世界が大きく広がる可能性があります。
RA Family MCU + FSP (w Azure RTOS) + OpenAI AI技術で、これまで困難だった様々な課題を柔軟に解決できる未来を想像できる気がします。

色んな場面で活用していけたらと思います。
皆さんも使ってみてください。

# サンプルコード
以下のGithub Repositryにサンプルコード・プロジェクトを公開しています(時期未定)。参考にどうぞ！
・ https://github.com/omuraisu49/OpenAI_ChatGPT_Demo

# 参考資料
・ OpenAI API Reference: https://platform.openai.com/docs/api-reference
・ Reneas RA Flexible Software Package説明資料: https://renesas.github.io/fsp/
・ Azure RTOS NetX Duo Web HTTPS説明資料: https://learn.microsoft.com/ja-jp/azure/rtos/netx-duo/netx-duo-web-http/chapter1
・ Azure RTOS NetX Duo Web HTTPSサンプルコード: https://github.com/azure-rtos/netxduo/blob/master/samples/demo_netxduo_https.c
・ Renesas RA MCUウェブページ: https://www.renesas.com/ra

・Qiita記事「[Renesas RA開発で困ったらここを見ろ!](https://qiita.com/omuraisu49/items/d4a69bcd13929cf338d0)」
・Qiita記事「[Renesas RA：環境構築～シリアル出力時計開発](https://qiita.com/omuraisu49/items/b1ea9deb621abf7d030d)」
・Qiita記事「[Renesas RA：マイコンからLINE Notify](https://qiita.com/omuraisu49/items/bfb4525e07c79d63b2d8)」

（以上）