はじめに:ついにローカルを飛び出し、クラウドの世界へ

これまでは自宅のネットワーク内で完結していた電子工作。しかし、センサーで取得したデータを「どこからでも見たい」「保存したい」と思ったら、クラウドの出番です。

今回は、ESP32-S3から送信したデータを、Microsoftのサーバーレス環境であるAzure Functionsで受け取り、リアルタイムにログ出力させる仕組みを構築します。

この記事で分かること:

  • ESP32-S3からHTTPS通信でクラウドへデータ送信する方法
  • Azure Functions (Python v2) でHTTPリクエストを受け取る実装
  • SSL/TLS証明書エラーの回避策と開発時のベストプラクティス
  • JSONデータのやり取りとログストリームでの確認方法

システム構成:住所・部屋番号・合言葉

クラウドへデータを送るURLは非常に長く複雑ですが、分解すると実はシンプルです。

Azure Functions URLの構造

https://esp-connection-a5czdybffafbd8by.japanwest-01.azurewebsites.net/api/esp_data?code=UPeOWBUMenw6BVcySATk_...

これは以下の3つの要素で構成されています:

  1. 住所(ホスト名): esp-connection-xxxxx.japanwest-01.azurewebsites.net
    • データの送り先となるサーバーの名前です
  2. 部屋番号(ルートパス): /api/esp_data
    • サーバーの中の、どのプログラム(関数)を呼び出すか指定します
  3. 合言葉(関数キー): ?code=UPeOWBUMenw6BVcySATk_...
    • 「誰でも送っていいわけじゃない」というセキュリティのための鍵です

これらが合わさって、初めてクラウドと通信が成立します。

ESP32-S3からAzure Functionsへのデータフロー

ESP32-S3からAzure Functionsへのデータフロー


Azure Functionsの作成:フレックス従量課金で始める

まずは、データを受け取るサーバーレス環境を準備しましょう。今回は フレックス従量課金(Flex Consumption) プランを使用します。

フレックス従量課金プランの特徴

従来の従量課金プランと比較して、以下の利点があります:

  • より細かい課金単位: 実行時間に応じたよりきめ細かい課金
  • 高速なコールドスタート: 初回起動が速い
  • 柔軟なスケーリング: トラフィックに応じた自動スケール
  • 512MB~2048MB: メモリサイズを選択可能

1. Azure Portalでの作成手順

① リソースの作成

Azure Portal にアクセスし、「リソースの作成」から「Function App」を検索します。

② 基本設定

Azure Functions作成画面(フレックス従量課金)

Azure Functions作成画面(フレックス従量課金)

以下の項目を設定します:

項目 設定値 説明
サブスクリプション 使用するサブスクリプション お使いのAzureサブスクリプション
リソースグループ rg-ram(新規作成) 関連リソースをまとめる論理グループ
関数アプリ名 esp-connection グローバルで一意の名前(URLになります)
ランタイムスタック Python 今回はPythonを使用
バージョン 3.13 最新の安定版
リージョン Japan West 自宅から最も近いリージョン
インスタンスサイズ 512 MB 今回のシンプルな処理には十分

③ プラン設定

ホスティングプラン「Flex Consumption」 が選択されていることを確認します。

④ ソース元設定

フレックス従量課金プランでは、ソース元の設定は無効になっています。これは正常な動作です。

⑤ 作成実行

「確認および作成」→「作成」をクリックし、デプロイが完了するまで待ちます(通常2〜3分)。

2. コードのデプロイとトリガーの定義

Python v2モデルでは、トリガーの種類はAzure Portalではなく、コードで定義します。 これが従来のモデルとの大きな違いです。

後述の function_app.py をデプロイすることで、自動的にHTTP Triggerが認識されます:

@app.route(route="esp_data")  # ← ここでHTTP Triggerを定義
def http_trigger(req: func.HttpRequest) -> func.HttpResponse:
    # 処理内容

デプロイ方法は以下の選択肢があります:

  • VS Code拡張機能:Azure Functions拡張機能から直接デプロイ(推奨)
  • Azure CLIfunc azure functionapp publish esp-connection コマンド
  • GitHub Actions:CI/CDパイプラインでの自動デプロイ

3. 関数キー(Function Key)の取得

コードをデプロイ後、ESP32から接続するためのURLと認証キーを取得します。

  1. Azure Portal で作成した Function App を開く
  2. デプロイが完了すると、左メニューの「関数」に esp_data が表示される
  3. esp_data 関数を開く
  4. 上部メニューから「関数キーの取得」をクリック
  5. 表示されたURLをコピー(?code=... まで含む)

このURLが、ESP32側のコードで設定する AZURE_URL になります。

// ESP32側で設定するURL(実際の値に置き換えてください)
#define AZURE_URL "https://esp-connection-xxxxx.japanwest-01.azurewebsites.net/api/esp_data?code=..."

Azure Functionsのトリガーとは?今回使う「HTTP Trigger」

Azure Functionsは、様々なトリガー(きっかけ)によって関数を実行できるサーバーレスプラットフォームです。今回はHTTP Triggerを使用しますが、他にも多彩なトリガーが用意されています。

HTTP Trigger:今回の主役

HTTP Triggerは、HTTPリクエスト(GETやPOST)を受け取ると関数が実行されるトリガーです。

Python v2モデルでは、@app.route()デコレーターでHTTP Triggerを定義します:

@app.route(route="esp_data")  # ← HTTP Triggerの定義
def http_trigger(req: func.HttpRequest) -> func.HttpResponse:
    # この関数が /api/esp_data へのHTTPリクエストで実行される
    pass

特徴

  • 外部からのアクセスが可能: ESP32などのIoTデバイスからURLを指定して呼び出せる
  • REST API構築に最適: Web APIやWebhookの実装に使われる
  • 認証レベルの設定: 匿名、関数キー、管理キーから選択可能
  • コードで完結: Azure Portal での設定不要(Python v2の利点)

今回は**関数キー認証(AuthLevel.FUNCTION)**を使用し、?code=...を含むURLでのみアクセスできるようにします。

その他の主要なトリガー一覧

Azure Functionsには、用途に応じた多様なトリガーが用意されています。Python v2モデルでは、それぞれ専用のデコレーターでトリガーを定義します:

1. Timer Trigger(タイマートリガー)

指定した時間間隔で定期実行されます。

  • 用途: 毎日午前9時にレポートを生成、1時間ごとにデータをバックアップ
  • : @app.schedule(schedule="0 0 9 * * *", ...)(Cron形式で毎日午前9時)

2. Blob Trigger(ストレージトリガー)

Azure Blob Storageにファイルがアップロードされると実行されます。

  • 用途: 画像がアップロードされたら自動でサムネイル生成、CSVファイルを自動解析
  • : @app.blob_trigger(path="container/{name}", ...)

3. Queue Trigger(キュートリガー)

Azure Queue Storageにメッセージが追加されると実行されます。

  • 用途: 非同期タスク処理、メッセージベースの疎結合アーキテクチャ
  • : @app.queue_trigger(queue_name="myqueue", ...)

4. Event Grid Trigger(イベントグリッドトリガー)

Azure内の様々なイベント(リソース作成、削除など)に反応します。

  • 用途: リソース監視、自動応答システム
  • : @app.event_grid_trigger(...)

5. Cosmos DB Trigger(データベーストリガー)

Cosmos DBのデータが変更されると実行されます。

  • 用途: データ変更のリアルタイム通知、変更履歴の記録
  • : @app.cosmos_db_trigger(database_name="db", collection_name="col", ...)

6. Service Bus Trigger(サービスバストリガー)

Azure Service Busのキューやトピックにメッセージが届くと実行されます。

  • 用途: エンタープライズメッセージング、複雑なワークフロー
  • : @app.service_bus_queue_trigger(queue_name="myqueue", ...)

なぜHTTP Triggerを選んだのか?

今回の要件(ESP32からのデータ受信)には、HTTP Triggerが最適です:

判断基準 HTTP Trigger その他のトリガー
外部デバイスからのアクセス ✅ URLで直接呼び出し可能 ❌ Azure内部からのみ
シンプルさ ✅ HTTPリクエストだけで完結 ⚠️ 中間ストレージが必要
リアルタイム性 ✅ 即座にレスポンス ⚠️ ポーリング間隔に依存
実装のしやすさ ✅ 標準のHTTPクライアントで可能 ❌ SDKや専用ライブラリが必要

IoTデバイスからのデータ送信には、HTTP Triggerが最もシンプルで効果的な選択肢です。


Azure側の準備:Python v2モデルのポイント

今回は、最新のPython v2プログラミングモデルを使用します。

重要:ファイル名は function_app.py 固定

以前のモデルとは異なり、このモデルではファイル名を必ず function_app.py にする必要があります。 これを間違えると、Azure側で関数が認識されないため注意が必要です。

実装のキモ:データ受信とログ出力

以下が今回使用する、データを受信してログに書き出すシンプルなコードです。

import azure.functions as func
import logging
import json

app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)

@app.route(route="esp_data")
def http_trigger(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    try:
        # 1. ESP32から送られてきたJSONを取得
        req_body = req.get_json()
        
        # 2. ログに出力(Azureポータルの「ログ」で見れます)
        device_id = req_body.get('device_id')
        temp = req_body.get('temperature')
        humi = req_body.get('humidity')
        
        logging.info(f"Received from {device_id}: Temp={temp}, Humi={humi}")

        # 3. ESP32へレスポンスを返す
        return func.HttpResponse(
            f"Success: Received data from {device_id}",
            status_code=200
        )
    except ValueError:
        return func.HttpResponse(
             "Invalid JSON",
             status_code=400
        )

コードのポイント

  • @app.route(route="esp_data"): エンドポイントのパスを定義(/api/esp_dataになる)
  • req.get_json(): HTTP POSTで送られてきたJSONボディを解析
  • logging.info(): Azureのログストリームに出力される
  • エラーハンドリング: 不正なJSONが送られた場合は400エラーを返す

ESP32側の実装:HTTPS通信の壁を越える

ESP32-S3からAzureにデータを送る際、セキュリティのために**HTTPS(SSL/TLS)**を使用します。

1. 必須ライブラリ:cJSON と HTTP Client

ESP-IDFには標準で以下が含まれています:

  • esp_http_client: HTTP/HTTPS通信を行うライブラリ
  • cJSON: JSONデータの作成・解析ライブラリ
  • esp_crt_bundle: ルート証明書バンドル

2. SSL/TLS設定の3つのポイント

HTTPS通信を成功させるための重要な設定です:

esp_http_client_config_t config = {
    .url = AZURE_URL,
    .method = HTTP_METHOD_POST,
    .transport_type = HTTP_TRANSPORT_OVER_SSL,       // ① HTTPS通信を有効化
    .skip_cert_common_name_check = true,            // ② 開発時:証明書検証をスキップ
    .crt_bundle_attach = esp_crt_bundle_attach,     // ③ ルート証明書バンドルを使用
};

HTTP_TRANSPORT_OVER_SSL

これを指定することで、HTTP通信がHTTPSに切り替わります。

skip_cert_common_name_check = true(開発時のテクニック)

HTTPS通信では「相手が本当に正しいサーバーか」を確認する証明書検証が行われます。しかし、初心者が最初につまずくのもここです。

開発時は .skip_cert_common_name_check = true を設定することで、検証を一旦スキップし、「まずは通信を成功させる」 ことを優先します。

⚠️ 本番環境では必ず削除してください。 セキュリティリスクになります。

crt_bundle_attach

Azureのような信頼できるサーバーの証明書を検証するために、ESP32にルート証明書バンドルを埋め込みます。

3. JSONデータの作成と送信

センサーデータをJSON形式にまとめて送信します:

// JSONデータの作成
cJSON *root = cJSON_CreateObject();
cJSON_AddNumberToObject(root, "temperature", 24.8);
cJSON_AddNumberToObject(root, "humidity", 55);
cJSON_AddStringToObject(root, "device_id", "ESP32-S3-Azure");

char *post_data = cJSON_PrintUnformatted(root);

// HTTPクライアントの初期化と送信
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_http_client_set_header(client, "Content-Type", "application/json");
esp_http_client_set_post_field(client, post_data, strlen(post_data));

esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
    ESP_LOGI(TAG, "Azure Success! Status = %d", 
             esp_http_client_get_status_code(client));
} else {
    ESP_LOGE(TAG, "Azure Failed: %s", esp_err_to_name(err));
}

// メモリ解放
esp_http_client_cleanup(client);
cJSON_Delete(root);
free(post_data);

4. Wi-Fi接続の自動リトライ

クラウド連携では、ネットワークの安定性が重要です。Wi-Fi切断時に自動再接続する仕組みを実装します:

static void event_handler(void* arg, esp_event_base_t event_base,
                          int32_t event_id, void* event_data) {
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        esp_wifi_connect();
        ESP_LOGI(TAG, "Retry connecting to Azure...");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

運命の瞬間:ログストリームでの確認

実装ができたら、Azureポータルの「ログ ストリーム」を開いて、ESP32-S3を起動しましょう。

ログストリームの表示方法

  1. Azure Portal で Function App を開く
  2. 左メニューから「ログ ストリーム」を選択
  3. ESP32-S3の電源を入れてデータ送信を開始
Azureポータルのログ画面。ESP32からのデータが届く瞬間

Azureポータルのログ画面。ESP32からのデータが届く瞬間

画面に以下のようなログが表示されれば成功です!

2026-02-01T12:34:56.789 [Information] Received from ESP32-S3-Azure: Temp=24.8, Humi=55

サーバーレスの応答性は非常に高く、送信からわずか数ミリ秒で処理されるその速さを実感できるはずです。


トラブルシューティング:よくあるエラーと対処法

1. ESP_ERR_ESP_TLS_FAILED_CONNECT_TO_HOST

原因: SSL/TLS証明書の検証に失敗しています。

対処法:

// menuconfig でルート証明書バンドルを有効化
Component config  ESP-TLS  [*] Enable trusted root certificate bundle

2. 401 Unauthorized

原因: 関数キー(?code=...)が間違っているか、含まれていません。

対処法:

  • Azure Portal で関数キーを再確認
  • URLに ?code=...必ず含まれていることを確認

3. 400 Bad Request

原因: 送信しているJSONの形式が間違っています。

対処法:

// Content-Typeヘッダーを必ず設定
esp_http_client_set_header(client, "Content-Type", "application/json");

// JSONが正しく生成されているか確認
ESP_LOGI(TAG, "Sending JSON: %s", post_data);

まとめ:クラウド連携で広がる可能性

今回の手順で、「クラウドにデータを届ける」という強固な土台が完成しました。

実現できたこと

データが届く: function_app.py が正しく動作し、ESP32からのデータを受信
安全に届く: HTTPSでセキュアに送信
構造化される: JSON形式でデバイス名や値を整理
リアルタイム性: 数ミリ秒での応答を実現

これから広がる応用例

この土台ができれば、以下のような応用が可能になります:

  • 📊 データベースに保存: Cosmos DBやSQL Databaseに蓄積して時系列分析
  • 📱 異常通知: 温度が閾値を超えたらLINEやメールで通知
  • 📈 可視化ダッシュボード: Power BIやGrafanaでリアルタイム監視
  • 🤖 AI連携: Azure Cognitive Servicesで画像認識や音声認識
  • 🌐 複数デバイス管理: IoT Hubを使った大規模デバイス制御

可能性は無限大です。 まずは今回の基礎をしっかりマスターしましょう。


技術資料とコード

今回の完全なソースコードはGitHubで公開しています。

📦 GitHubでコードを見る

プロジェクト構成

esp32-azure-demo/
├── main/
│   └── main.c          # ESP32-S3側のメインコード
├── azure/
│   └── function_app.py # Azure Functions Python v2コード
└── README.md

動作環境

ESP32側:

  • ESP-IDF v5.5.2
  • ESP32-S3 DevKitC-1
  • Wi-Fi 2.4GHz対応環境

Azure側:

  • Azure Functions Python 3.11
  • Linux Consumption Plan
  • 無料枠で動作可能

おわりに

IoTデバイスとクラウドの連携は、最初は難しく感じるかもしれません。しかし、今回のように一歩ずつ積み上げることで、確実に動くシステムが作れます。

今後、このデータをCosmos DBに保存し、時系列データとして蓄積・可視化する方法を解説予定です。お楽しみに!


関連記事: