ESP32からのJSONを、データサイズ1/3・処理速度2.7倍 のgRPCに変えたら何が変わったか——実際に計測した結果をそのままお見せします。ただし、意外な落とし穴もありました。
「もっとデータを軽くしたい」「マイコンの処理負荷を下げたい」「バッテリーを長持ちさせたい」——こうした課題に対して有力な選択肢として挙がるのが gRPC(Protocol Buffers) です。本記事では ESP32-S3 と Azure Functions を使って実際に計測し、速度・サイズ・省電力化の面で何が違うのかをデータで比較します。
- gRPC(Protobuf/Nanopb)とJSONのシリアライズ速度・データサイズの実測値
- データ1/3なのに通信時間が変わらない理由(TLSハンドシェイクの正体)
- ESP32でgRPCを実装する流れ(.protoファイル→Nanopb→Azure Functions)
- gRPCが省電力・省コストに貢献する具体的なシーン
- gRPCとJSONどちらを選ぶべきか【判断チェックリスト付き】
📊 実測データ比較:gRPC vs JSON
まず結論から。同じセンサーデータ(デバイスID・温度・湿度)を ESP32-S3 から Azure Functions へ送信した実測値です。
| 比較項目 | JSON形式 | gRPC (Protobuf/Nanopb) | 改善率 |
|---|---|---|---|
| シリアライズ時間 | 約424 μs | 約157 μs | 約2.7倍高速 |
| データサイズ | 62 bytes | 21 bytes | 約1/3に圧縮 |
| トータル通信時間 | 約1.3 sec | 約1.3 sec | 差なし |
シリアライズ速度とデータサイズでは gRPCが圧倒的な優位性を示しました。一方、トータルの通信時間はどちらもほぼ同じという、直感に反する結果も出ています。この理由は後述します。
🔍 gRPCとNanopbとは?ESP32での採用理由
Protocol Buffers(Protobuf) は、Googleが開発したバイナリ形式のデータシリアライズフォーマットです。JSONと同様にデータ構造を定義できますが、テキストではなくバイナリで扱うため、サイズが小さく処理が速いという特徴があります。
gRPC はこのProtobufを用いたRPC(Remote Procedure Call)フレームワークで、マイクロサービスやIoTバックエンドとの通信で広く採用されています。
ESP32のようなリソース制約のあるマイコンでProtobufを使う場合は、組み込み向けの軽量実装 Nanopb を使うのが定番です。通常のProtobufライブラリはメモリを多く使うため、マイコンには向きません。
ProtobufとgRPCの理論的な基礎はこちら: 【理論解説】マイコンで使うgRPC入門!Protocol Buffers基礎知識
🛠️ 実装の流れ:ESP32(Nanopb)× Azure Functions
① .protoファイルでデータ構造を定義する
gRPCの出発点は .proto ファイルです。送受信するデータの構造をここで定義します。
// sensor_data.proto
syntax = "proto3";
message SensorData {
string device_id = 1;
float temperature = 2;
float humidity = 3;
}
このファイルから、Nanopb が ESP32 用の C コード(.pb.h / .pb.c)を自動生成します。手書きのJSONと異なり、型が保証されたコードが自動生成されるため、実装ミスが減ります。
② ESP32(Nanopb)でシリアライズして送信する
#include "sensor_data.pb.h"
#include <pb_encode.h>
// データ構造体に値をセット
SensorData message = SensorData_init_zero;
strncpy(message.device_id, "esp32-s3-01", sizeof(message.device_id));
message.temperature = 25.4f;
message.humidity = 60.2f;
// バッファにエンコード
uint8_t buffer[128];
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
pb_encode(&stream, SensorData_fields, &message);
size_t encoded_size = stream.bytes_written; // → 21 bytes
手書きでJSONを組み立てる必要がなく、構造体への代入だけでシリアライズが完了します。
③ HTTPS で Azure Functions へ送信する
// タスクのスタックサイズは TLS通信のため十分に確保すること
xTaskCreate(
https_post_task,
"https_task",
16384, // 16KB 推奨(8KB未満だとクラッシュしやすい)
NULL,
5,
NULL
);
HTTPS(TLS)通信はメモリを多く消費します。スタックサイズが不足するとクラッシュします。xTaskCreate の第3引数は 16384 バイト(16KB)以上 を推奨します。
Nanopb の環境構築とローカル疎通確認の詳細手順はこちら: ESP32でgRPC(Protobuf)通信!Nanopbを使って爆速・軽量な自動ビルド環境を構築する(ローカル疎通)
④ Azure Functions(Python)でバイナリを受信・デコードする
import azure.functions as func
from sensor_data_pb2 import SensorData
def main(req: func.HttpRequest) -> func.HttpResponse:
body = req.get_body() # バイナリデータを受信
data = SensorData()
data.ParseFromString(body) # Protobufでデコード
print(f"device: {data.device_id}, temp: {data.temperature}, hum: {data.humidity}")
return func.HttpResponse("OK", status_code=200)
Content-Type application/octet-stream でリクエストを受信し、Pythonの protobuf ライブラリで即座に構造体へ復元できます。
ESP32とAzure Functionsの連携方法の基礎はこちら: ESP32-S3からAzureへデータを飛ばす!HTTPS POSTとサーバーレス連携の極意
⚡ なぜデータ1/3でも通信時間が同じ?TLSハンドシェイクの正体
実測でトータル通信時間に差が出なかったのは、TLS(HTTPS)ハンドシェイクが通信時間を支配しているからです。
通信時間の内訳(実測):
| フェーズ | 所要時間 |
|---|---|
| TLSハンドシェイク | 約1,200〜1,300 ms |
| データ送信 | 30〜50 ms |
| レスポンス受信 | 20〜40 ms |
ハンドシェイクが全体の 95%以上 を占めており、データサイズを1/3に削減しても通信時間への影響はほぼゼロです。
「gRPCを使えば通信が速くなる」は誤りです。gRPCの真価は 通信時間の短縮 ではなく、CPU負荷の削減・データ量の削減・省電力化・型安全な開発 にあります。
🔋 gRPCが効果を発揮する3つのシーン
1. バッテリー駆動のIoTデバイス(省電力化)
シリアライズ時間が424μs → 157μsに短縮されることで、CPUがフル稼働する時間が減ります。その分早く Deep Sleep へ移行でき、バッテリー寿命の延長につながります。
マイクロ秒単位の差に思えますが、1日数百〜数千回の送信を繰り返す場合、この積み重ねが電池交換頻度に影響します。
2. LTE-Mや従量課金回線(通信コスト削減)
21バイト vs 62バイトの差は、通信コストが重要なシーンで直結します。
- 従量課金回線(LTE-M、Cat-M1など) での月額コスト削減
- 電波が弱い環境 での送信成功率向上
- 複数センサーの同時送信 での帯域節約
1センサーあたり数十バイトの削減でも、数百台・数千台のデバイスに展開すると無視できない差になります。
3. 大規模開発・チーム開発(型安全な設計)
.proto ファイルをマイコン側・クラウド側で共有することで、データ型の不一致によるバグをコンパイル時に検出できます。
- データ構造の変更が
.protoファイルの編集だけで完結 - マイコン(C)とサーバー(Python/Go/Node.js)間の型互換を保証
- ドキュメントとしても機能するため、保守性が向上
✅ gRPC vs JSON:どちらを選ぶべきか【判断チェックリスト】
| 条件 | おすすめ |
|---|---|
| バッテリー駆動・省電力を重視する | gRPC |
| LTE-Mなど従量課金回線を使う | gRPC |
| 複数言語・チーム開発で型安全を確保したい | gRPC |
| 大量のセンサーデータを継続送信する | gRPC |
| プロトタイプ・少量データ・Wi-Fi固定 | JSON |
| デバッグ優先・人が読める形式が必要 | JSON |
| サーバー側が既存REST APIで変更不可 | JSON |
📦 ソースコード
今回の検証に使用したコード(ESP-IDF / Azure Functions)はGitHubで公開しています。
実装の詳細・ビルド手順はリポジトリの README で解説しています。
実装手順の詳細はQiita記事でも解説しています: ESP32 × Azure Functions!gRPC (Protobuf) vs JSON 通信比較検証(クラウド連携)
まとめ
ESP32-S3とAzure Functionsを使ったgRPC(Nanopb)とJSONの通信比較をまとめます。
- シリアライズ速度:gRPCが約2.7倍高速(424μs → 157μs)
- データサイズ:gRPCが約1/3に圧縮(62 bytes → 21 bytes)
- トータル通信時間:TLSハンドシェイクに支配されるため差なし(どちらも約1.3秒)
「通信を速くする」ことが目的であれば、gRPCより TLS再接続の回避やMQTT等の別プロトコルの採用 を検討する方が効果的です。一方、バッテリー寿命の延長・通信コスト削減・型安全な大規模開発 を重視するなら、gRPCは非常に有力な選択肢です。
See you …
よくある質問(FAQ)
Q. ESP32でgRPCを使うにはどのライブラリが必要ですか?
ESP32(ESP-IDF)では Nanopb を使います。Arduino環境では nanopb ライブラリが使えますが、ESP-IDFの方が安定して動作します。.protoファイルからC言語コードを自動生成する protoc と nanopb_generator.py が必要です。
Q. ESP32でgRPCを使うならNanopbとEmbeddedProtoどちら?
Nanopbは C言語向け、EmbeddedProtoはC++向けの組み込みProtobuf実装です。ESP32はどちらも使えますが、ESP-IDFとの相性や実績の多さからNanopbが多く使われています。
Q. ESP32のgRPC通信はJSONより省電力になりますか?
シリアライズ処理が2.7倍速い分、CPUが高負荷状態にある時間が短くなり、早くDeep Sleepへ移行できます。特にバッテリー駆動デバイスで1日数百〜数千回送信する場合に効果が出ます。ただしTLS接続のたびに1秒以上かかるため、送信頻度を減らすか接続を維持する設計の方が省電力効果は大きいです。
Q. Azure FunctionsでProtobuf(gRPC)を受け取るにはどうすればいいですか?
Content-Type application/octet-stream でHTTP POSTを受信し、Python の protobuf ライブラリ(pip install protobuf)でデコードします。HTTPを使う場合はgRPC本来の双方向ストリーミングは使えませんが、Protobufのシリアライズ効果は得られます。
Q. ESP32でTLSハンドシェイクを短縮する方法はありますか?
完全に回避するのは難しいですが、以下の方法で改善できます。①セッション再開(TLS Session Resumption)の活用、②MQTT+TLSなど常時接続プロトコルへの切り替え、③HTTP/2の活用(1接続で複数リクエスト)。用途によっては最初からMQTTを検討する方が根本的な解決になります。