「ESP32でWebサーバーは作れたけど、センサー値をリアルタイムで表示したい…ページを更新するたびに値が変わるのはちょっと違うよね」

そう思ったことはありませんか?

HTTPのリクエスト・レスポンス方式では、ブラウザ側から「くれ」と言うたびにデータが返ってくる仕組みです。でも温湿度センサーや照度センサーの値をページリロードなしで自動更新したいなら、WebSocketがぴったりなんです。

今回は ESP32 と WebSocket を使って、ブラウザとマイコンが双方向にリアルタイム通信する仕組みをゼロから実装します。センサー値のライブ表示だけでなく、ブラウザからESP32のLEDをON/OFFするところまで一気につくってみましょう! 🚀

WebSocketとHTTPの違いをざっくりおさえよう

ESP32 IoT dashboard
ESP32 IoT dashboard / Photo by Tanha Tamanna Syed via Pexels

まず、WebSocket が何者かをサクッと確認しておきましょう。

HTTPは「客が注文してはじめて料理が出てくる」方式です。ブラウザが「データをください」と送らないかぎり、ESP32は何もしません。一方 WebSocket は「カウンター越しに常に話せる状態」のようなイメージです。一度コネクションを張ったら、ESP32側からもブラウザ側からもいつでも自由にデータを送り合えるんです。

  • ✅ ページリロード不要でセンサー値が更新される
  • ✅ ブラウザからボタン操作でESP32を即制御できる
  • ✅ 複数のブラウザを開いていても全員に同時配信できる

これがWebSocketの強みです。IoTダッシュボードとの相性は抜群ですよね。

必要なもの

  • ESP32開発ボード(どのモデルでもOK)
  • DHT22 または DHT11 温湿度センサー
  • Arduino IDE(ESP32ボードマネージャー導入済み)
  • ライブラリ:ESPAsyncWebServerAsyncTCPDHT sensor library

ライブラリはArduino IDEのライブラリマネージャーから検索してインストールしてください。「ESPAsyncWebServer」はGitHubからZIPで追加する場合もあります。

回路の配線

DHT22センサーをESP32に接続します。配線はシンプルです。

  • DHT22の VCC → ESP32の 3.3V
  • DHT22の GND → ESP32の GND
  • DHT22の DATA → ESP32の GPIO 4(10kΩプルアップ抵抗を3.3Vとの間に挿入)

また、動作確認用にGPIO 2のオンボードLEDをブラウザから制御します。

ArduinoIDEのスケッチ全体を書いてみよう

では実際のコードです。少し長く見えますが、ブロックごとに分けて読むと意外とシンプルです。

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include <DHT.h>

// ─── Wi-Fi設定 ───
const char* ssid     = "YOUR_SSID";       // 自分のSSIDに変更
const char* password = "YOUR_PASSWORD";   // 自分のパスワードに変更

// ─── センサー設定 ───
#define DHTPIN  4
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

// ─── LED設定 ───
#define LED_PIN 2

// ─── サーバー&WebSocketの初期化 ───
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");  // /ws エンドポイントでWebSocket通信

unsigned long lastSendTime = 0;
const long sendInterval = 2000;  // 2秒ごとにセンサー値を送信

// ─── WebSocketイベントハンドラ ───
void onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client,
               AwsEventType type, void* arg, uint8_t* data, size_t len) {

  if (type == WS_EVT_CONNECT) {
    Serial.printf("クライアント接続: id=%u\n", client->id());

  } else if (type == WS_EVT_DISCONNECT) {
    Serial.printf("クライアント切断: id=%u\n", client->id());

  } else if (type == WS_EVT_DATA) {
    // ブラウザからのメッセージを受け取る
    String msg = "";
    for (size_t i = 0; i < len; i++) {
      msg += (char)data[i];
    }
    Serial.printf("受信メッセージ: %s\n", msg.c_str());

    // LED制御コマンドを処理
    if (msg == "LED_ON") {
      digitalWrite(LED_PIN, HIGH);
      ws.textAll("LED:ON");  // 全クライアントに状態を通知
    } else if (msg == "LED_OFF") {
      digitalWrite(LED_PIN, LOW);
      ws.textAll("LED:OFF");
    }
  }
}

// ─── HTMLページ(ブラウザに返す) ───
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>ESP32 リアルタイムモニター</title>
  <style>
    body { font-family: sans-serif; text-align: center; padding: 40px; background: #f0f4f8; }
    .card { background: white; border-radius: 12px; padding: 24px;
            display: inline-block; min-width: 280px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
    h1 { color: #333; }
    .value { font-size: 2.5em; font-weight: bold; color: #2196F3; margin: 8px 0; }
    button { margin: 8px; padding: 12px 28px; font-size: 1em;
             border: none; border-radius: 8px; cursor: pointer; }
    #btnOn  { background: #4CAF50; color: white; }
    #btnOff { background: #f44336; color: white; }
    #ledStatus { margin-top: 12px; font-size: 1.1em; }
  </style>
</head>
<body>
  <div class="card">
    <h1>🌡 ESP32 センサーモニター</h1>
    <p>温度:<span class="value" id="temp">--</span> ℃</p>
    <p>湿度:<span class="value" id="humi">--</span> %</p>
    <hr>
    <p>LED制御</p>
    <button id="btnOn"  onclick="sendCmd('LED_ON')">ON</button>
    <button id="btnOff" onclick="sendCmd('LED_OFF')">OFF</button>
    <p id="ledStatus">LED: --</p>
  </div>

  <script>
    // WebSocket接続(ESP32のIPアドレスを自動取得)
    const ws = new WebSocket('ws://' + location.host + '/ws');

    ws.onmessage = function(event) {
      const msg = event.data;
      if (msg.startsWith('SENSOR:')) {
        // 例: "SENSOR:25.3,60.1" の形式でパース
        const parts = msg.replace('SENSOR:', '').split(',');
        document.getElementById('temp').textContent = parts[0];
        document.getElementById('humi').textContent = parts[1];
      } else if (msg === 'LED:ON') {
        document.getElementById('ledStatus').textContent = 'LED: ON 💡';
      } else if (msg === 'LED:OFF') {
        document.getElementById('ledStatus').textContent = 'LED: OFF 🌑';
      }
    };

    ws.onopen    = () => console.log('WebSocket接続OK');
    ws.onclose   = () => console.log('WebSocket切断');

    function sendCmd(cmd) {
      if (ws.readyState === WebSocket.OPEN) {
        ws.send(cmd);
      }
    }
  </script>
</body>
</html>
)rawliteral";

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  dht.begin();

  // Wi-Fi接続
  WiFi.begin(ssid, password);
  Serial.print("Wi-Fi接続中");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.print("IPアドレス: ");
  Serial.println(WiFi.localIP());  // このIPをブラウザで開く

  // WebSocketハンドラを登録
  ws.onEvent(onWsEvent);
  server.addHandler(&ws);

  // ルートにHTMLを返す
  server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
    request->send_P(200, "text/html", index_html);
  });

  server.begin();
  Serial.println("サーバー起動完了");
}

void loop() {
  // 定期的にセンサー値をWebSocketで全クライアントに送信
  if (millis() - lastSendTime >= sendInterval) {
    lastSendTime = millis();

    float temp = dht.readTemperature();
    float humi = dht.readHumidity();

    if (!isnan(temp) && !isnan(humi)) {
      String payload = "SENSOR:" + String(temp, 1) + "," + String(humi, 1);
      ws.textAll(payload);  // 全接続クライアントに送信
      Serial.println("送信: " + payload);
    } else {
      Serial.println("センサー読み取りエラー");
    }
  }

  ws.cleanupClients();  // 切断済みクライアントの後始末
}

ポイントをまとめるとこんな感じです。

  • 📡 ws.textAll() を使うと、接続中の全クライアントに同時配信できる
  • 🔄 loop() 内で millis() を使い、2秒ごとにセンサー値を自動送信する(delay() は使わない)
  • 💬 ブラウザ→ESP32の通信は WS_EVT_DATA イベントで受け取る
  • 🧹 ws.cleanupClients() を忘れずに呼ぶ。メモリリーク対策に重要です

動かしてみよう!確認手順


コードを書き込んだら、以下の手順で動作確認してみましょう。

STEP 1:シリアルモニタを開いて Wi-Fi 接続と IP アドレスを確認する
STEP 2:表示された IP アドレス(例: 192.168.1.XX)をブラウザのアドレスバーに入力
STEP 3:2秒ごとに温度・湿度の数値がリアルタイムで更新されることを確認
STEP 4:「ON」「OFF」ボタンを押して LED が即座に点灯・消灯することを確認

複数のタブやスマホのブラウザからも同じIPに接続してみてください。どのクライアントからボタンを押しても、全員の画面に LED の状態変化が反映されるはずです。これが WebSocket の双方向・多クライアント通信の醍醐味ですよね 😊

よくあるトラブルと対処法


センサー値が「–」のまま変わらない

DHT センサーの読み取りに失敗しています。シリアルモニタで「センサー読み取りエラー」が出ていたら配線を再確認しましょう。プルアップ抵抗(10kΩ)が抜けているケースが多いです。

ページは開けるけどリアルタイム更新されない

ブラウザのコンソール(F12)を開いて「WebSocket接続OK」が表示されているか確認しましょう。「ws://」で接続しているかどうかも重要です。HTTPSが有効な環境では「wss://」が必要な場合があります。

ESPAsyncWebServer のビルドエラーが出る

AsyncTCP ライブラリが入っていない場合に起きやすいです。Arduino IDE のライブラリマネージャーで「AsyncTCP」を検索してインストールしてください。

まとめ

今回は ESP32 と WebSocket を組み合わせて、センサー値のリアルタイム表示とブラウザからのLED制御を実装しました。

  • ✅ WebSocket は HTTP と違い、一度つなげば双方向通信がずっと続く
  • ws.textAll() で全クライアントへ同時配信できる
  • ✅ ブラウザ側は標準の JavaScript API だけで動く(追加ライブラリ不要!)

HTTPだけのIoTダッシュボードと比べると、操作感がまるで違います。一度リアルタイム更新の気持ちよさを体験してしまうと、もう戻れないかもしれません(笑)。

次のステップとしては、センサーの種類を増やしたり、Chart.js でグラフ表示にしたりするとさらに実用的なダッシュボードになりますよ。ぜひ試してみてください! 🎉

📡 Arduinoをもっと深く学ぼう!

Arduino・ラズパイ・ロボットプログラミングを体系的に学びたい方へ。おすすめのUdemyコースや電子部品もまとめています。

Arduinoロボット入門のおすすめコース・部品を見る →

📚 関連商品・おすすめ書籍

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

もしも

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

初心者に定番のPython入門書

Amazonで見る

Arduinoをはじめよう 第4版 (Make: PROJECTS)

もしも

Arduinoをはじめよう 第4版 (Make: PROJECTS)

Arduino公式推薦の定番入門書

Amazonで見る

徹底攻略! 電子工作&プログラミング Arduinoで学ぶ電子工作完全ガイド

もしも

徹底攻略! 電子工作&プログラミング Arduinoで学ぶ電子工作完全ガイド

電子工作とプログラミングを同時に学べる

Amazonで見る

※本記事にはアフィリエイトリンクが含まれます。

ABOUT ME
やまちゃん
これまで学生と社会人を合わせて5000人以上にプログラミング学習を指導。 ゼロからイチをわかりやすく解説する専門家として活動しており、本業ではArduinoを用いたIoT開発とロボットプログラミングが専門。 Pythonを用いたアプリ開発、ウェブアプリケーションの開発で業務の効率化をサポートしています。