連載一覧
- 第1回:プロジェクト概要
- 第2回:ESP32-S3 の環境構築
- 第3回:I/O の実装(ADC / PWM / DIO)
- 第4回:通信プロトコル(JSON コマンド)
- 第4.5回:デバイス化対応
- 第5回:Python アプリの API 実装
- 第6回:デバイスとしてまとめる(設計編)
- 第7回:実装編・応用編(GUI / アプリ化 / 拡張の可能性)
はじめに
前回までで、ESP32‑S3 の環境構築と I/O の基本動作を確認しました。
今回は、Python アプリと ESP32‑S3 をつなぐ通信プロトコルを設計します。
このプロジェクトでは、USB シリアルを使って PC と ESP32‑S3 を接続し、JSON 形式のコマンドで Python から I/O を操作できるようにします。
• Python → ESP32:JSON コマンドを送信
• ESP32 → Python:JSON レスポンスを返す
この仕組みを作ることで、次回の「Python API 実装」を実現します。
本記事では VisualStudioCode で Python を扱います。
環境構築についてはこちらの記事がわかりやすいです。
VisualStudioCodeでPython環境構築まとめ
JSON コマンドの考え方
ESP32‑S3 に送るコマンドはすべて JSON で統一します。
例:ADC を読みたい場合
{"cmd": "read_adc", "pin": 1}
詳しく知りたい方はこちらの記事をどうぞ
JSONとは?初心者向けに基本構造と使い方をわかりやすく解説
なぜ JSON なのか
- Python から扱いやすい
- 拡張しやすい
- デバッグしやすい
- 人間が読める
ESP32‑S3 はこの JSON をパースし、コマンド(cmd)の内容に応じて処理を実行します。
コマンド一覧
| コマンド | 説明 | パラメータ例 |
|---|---|---|
| read_adc | ADC値を取得 | {"cmd":"read_adc","pin":1} |
| set_pwm | PWM出力を設定 | {"cmd":"set_pwm","pin":2,"duty":128} |
| read_di | デジタル入力を取得 | {"cmd":"read_di","pin":3} |
| set_do | デジタル出力を設定 | {"cmd":"set_do","pin":4,"value":1} |
| ping | 通信確認 | {"cmd":"ping"} |
ESP32‑S3 側の受信ループ
ESP32‑S3 は、USB シリアルから 1 行ずつ JSON を受け取り、内容に応じて処理を分岐します。以下のコードをESP32-S3に書き込むと、JSONコマンドを受け付けられる状態になります。
基本の流れ
- シリアルから 1 行読み取る
- JSON をパース
- cmd に応じて処理
- 結果を JSON で返す
サンプルコード(ESP32-S3側)
以下は「最小構成の JSON コマンド受信ループ」です。
※今回はピン番号をコマンドで指定していますが、自由に指定できてしまうと誤設定やノイズの影響を受けやすくなります。そのため、今後デバイス化する際は「このピンは入力」「このピンはPWM」など役割を固定する予定です。
#include <Arduino.h>
#include <ArduinoJson.h>
void setup() {
Serial.begin(115200);
}
void loop() {
if (!Serial.available()) return;
// 1 行の JSON を受信
String line = Serial.readStringUntil('\n');
// JSON パース
StaticJsonDocument<256> doc;
auto err = deserializeJson(doc, line);
if (err) {
Serial.println("{\"status\":\"error\",\"message\":\"json parse error\"}");
return;
}
const char* cmd = doc["cmd"];
// ----------------------------------------------------------------------
// デジタル入力 read_di
// {"cmd":"read_di","pin":5}
// ----------------------------------------------------------------------
if (strcmp(cmd, "read_di") == 0) {
int pin = doc["pin"];
pinMode(pin, INPUT);
int value = digitalRead(pin);
StaticJsonDocument<128> res;
res["status"] = "ok";
res["value"] = value;
serializeJson(res, Serial);
Serial.println();
}
// ----------------------------------------------------------------------
// デジタル出力 set_do
// {"cmd":"set_do","pin":5,"value":1}
// ----------------------------------------------------------------------
else if (strcmp(cmd, "set_do") == 0) {
int pin = doc["pin"];
int value = doc["value"]; // 0 or 1
pinMode(pin, OUTPUT);
digitalWrite(pin, value ? HIGH : LOW);
Serial.println("{\"status\":\"ok\"}");
}
// ----------------------------------------------------------------------
// ADC 読み取り read_adc
// {"cmd":"read_adc","pin":1}
// ----------------------------------------------------------------------
else if (strcmp(cmd, "read_adc") == 0) {
int pin = doc["pin"];
int value = analogRead(pin);
StaticJsonDocument<128> res;
res["status"] = "ok";
res["value"] = value;
serializeJson(res, Serial);
Serial.println();
}
// ----------------------------------------------------------------------
// PWM 出力 set_pwm
// {"cmd":"set_pwm","pin":2,"duty":128}
// ----------------------------------------------------------------------
else if (strcmp(cmd, "set_pwm") == 0) {
int pin = doc["pin"];
int duty = doc["duty"];
ledcAttach(pin, 5000, 8); // 5kHz / 8bit
ledcWrite(pin, duty);
Serial.println("{\"status\":\"ok\"}");
}
// ----------------------------------------------------------------------
// ping
// {"cmd":"ping"}
// ----------------------------------------------------------------------
else if (strcmp(cmd, "ping") == 0) {
Serial.println("{\"status\":\"ok\",\"message\":\"pong\"}");
}
// ----------------------------------------------------------------------
// 不明コマンド
// ----------------------------------------------------------------------
else {
Serial.println("{\"status\":\"error\",\"message\":\"unknown command\"}");
}
}
コンパイルには ArduinoJson が必要です。
ライブラリマネージャーから「ArduinoJson by Benoit lanchon ...」をインストールしてから書き込みを行ってください。

レスポンス形式
ESP32‑S3 はシリアル通信でJSONコマンドを受け取り実行すると、必ず 1 行の JSON を返します。
| 実行結果 | 応答例 |
|---|---|
| 成功 | {"status":"ok","value":1234} |
| エラー | {"status":"error","message":"invalid pin"} |
Python 側は この応答をreadline() で受け取り、JSON としてパースします。
デバッグ方法
シリアルモニタで手動テスト
Arduino IDE のシリアルモニタで JSON を送信できます。
| 送信コマンド | 応答(返り値) |
|---|---|
| {"cmd":"ping"} | {"status":"ok","message":"pong"} |
メッセージ入力欄に送信コマンドを入力してEnterを押すと、下部にESP32-S3からの応答が返ってきます。

前述の コマンド一覧 に記載している パラメータ例 を送信し、各コマンドに対しての応答が返ってくることを確認してください。
Python からの簡易テスト
Python からシリアル通信を扱うには pyserial が必要です。
VS Code などのターミナルから pip でインストールしてください。
pip install pyserial
サンプルコード(Python側)
以下は Python から ESP32-S3 に "ping" コマンドを送り、返ってきた JSON を表示する最小コードです。
※極力シンプルにするため、例外処理は省いています。
import serial, json
# "COM5" の部分は、Arduino IDE で書き込みに使ったポート番号に合わせてください
ser = serial.Serial("COM5", 115200, timeout=1)
# ESP32-S3 に ping コマンドを送信
ser.write(b'{"cmd":"ping"}\n')
# 1 行の JSON レスポンスを受信
line = ser.readline().decode().strip()
# JSON を Python の辞書に変換して表示
print(json.loads(line))
このコードを VisualStudioCode から実行して接続に成功すると、ESP32-S3 からの応答がターミナルに書き込まれます。

可変抵抗・LEDによる入出力確認
第3回で作った回路をそのまま使用して以下の確認ができます。
| 確認内容 | 送信コマンド |
|---|---|
| デジタル出力でLEDが点灯 | {"cmd":"set_do","pin":5,"value":1} |
| 可変抵抗値に応じてアナログ入力値が変化 | {"cmd":"read_adc","pin":10} |
| PWM出力のduty値に応じてLEDの明るさが変化 | {"cmd":"set_pwm","pin":5,"duty":128} |
まとめ
- ESP32‑S3 と Python の通信は JSON コマンドで統一
- ESP32‑S3 は「受信 → パース → 分岐 → JSON 返信」のループ
- Python 側は次回作る Device クラスから簡単に操作できるようになる
- デバッグはArduino IDEのシリアルモニタと Python の両方で可能
次回予告
次回は、今回の JSON プロトコルを使って
Python 側の Device クラス(API) を実装します。
- read_adc / set_pwm / read_di / set_do
- タイムアウト処理
- エラー処理
- 簡易テストツール
ESP32‑S3 を「USB でつなぐだけで使える I/O デバイス」に近づけていきます。
