- 概要
- PC[動画(p5.js)→色情報(RGB)]→MQTT→M5→LED
- Step1 PC側(p5.js):ブラウザ上で動画を再生する
- Step2 PC側(p5.js):ブラウザ上で動画を再生しつつ色情報を取得する
- Step3 PC側(p5.js):ブラウザ上で動画を再生しつつ色情報を取得し、MQTTでその色情報を送る
- Step4 M5側:受信してLEDを点灯させる ← 今ココ
- PC[動画(p5.js)→色情報(RGB)]-UDP→M5→LED
- 課題
Step4 M5側:受信してLEDを点灯させる

前回PC側で、動画のピクセル色をMQTTで送信する準備ができました。
このステップでは、M5StickC Plus2を「受信機」として設定し、その情報を受け取ってLEDを光らせます。
プログラムの全体像
このコードは、大きく分けて4つの役割を担っています。
- Wi-Fi接続:
- インターネットに接続して、MQTTサーバーと通信できるようにします。
- MQTT接続:
- MQTTサーバーに接続し、PCが送ってくる情報を受け取れるようにします。
- LED制御:
- 受け取った情報(RGB値)をもとに、LEDを光らせます。
- 画面表示:
- M5の画面に、現在の接続状態や受信した情報などを表示して、状況をわかりやすくします。
MQTT通信のルール
PC側とM5側で、同じ「トピック」という住所を使って情報をやり取りします。
- PC側:
m5class/roomA/任意ID/ledというトピックに「255,0,0」のような色情報を送信(publish)します。
- M5側:
m5class/roomA/というトピックを購読(subscribe)します。任意ID/led- 任意IDの箇所を「+」にするとなんでも受け入れるようになります。
+(プラス)は「ワイルドカード」と言って、「この部分はどんな名前でもOK」という意味です。- これにより、誰が送った情報でも受け取ることができます。

データの受け渡し
PCから送られる色情報は「255,0,0」のような文字列です。
M5側では、この文字列をsscanf()という関数を使って、それぞれの数字(255、0、0)に分解し、RGBそれぞれの値として扱えるようにします。
LEDの制御
コード内のAdafruit_NeoPixelというライブラリを使って、受け取ったRGB値でLEDの色を設定し、strip.show()という命令で実際に光らせます。
M5側:コード
※ひとまずトピックはtsuchidaのままでいいです。
※SSIDとPASSの設定忘れずに!
step04_led_subscriber_mqtt.ino(M5・受信側)
/**
* M5StickC Plus2 → test.mosquitto.org (MQTT over TLS) + NeoPixel
* 解説:
* - 公開ブローカー "test.mosquitto.org" のポート8883 (MQTTS) に接続します。
* - WiFiClientSecure を使い、TLS (SSL) で通信を暗号化します。
* - 証明書検証は setInsecure() でスキップし、手軽に接続できるようにしています。
* - 受信した "R,G,B" 文字列を解析し、NeoPixel LEDテープを光らせます。
*/
// 必要なライブラリを読み込みます
#include <M5Unified.h> // M5StickC Plus2の画面や電源管理などをまとめたライブラリ
#include <WiFi.h> // ESP32でWi-Fi接続を行うための標準ライブラリ
#include <WiFiClientSecure.h> // 安全な通信(TLS/SSL)を行うためのライブラリ(ポート8883用)
#include <PubSubClient.h> // MQTT通信を行うためのライブラリ
#include <Adafruit_NeoPixel.h> // テープLED(NeoPixel/WS2812B)を制御するライブラリ
// ======= Wi-Fi設定 =======
// 接続するWi-FiのSSID(名前)を設定します。自分の環境に合わせて書き換えてください。
const char* WIFI_SSID = "YOUR_SSID";
// 接続するWi-Fiのパスワードを設定します。
const char* WIFI_PASS = "YOUR_PASS";
// ======= MQTTサーバー設定 =======
// 1. test.mosquitto.org (公開サーバー・認証なし・TLSあり)
const char* MQTT_HOST = "test.mosquitto.org";
// ポート番号。8883は「暗号化あり(MQTTS)」の標準ポートです。
const int MQTT_PORT = 8883;
// ユーザー名とパスワード(公開サーバーなのでNULL=なし)
const char* MQTT_USER = NULL;
const char* MQTT_PASS = NULL;
/* // (参考) HiveMQ Cloud を使う場合
// const char* MQTT_HOST = "xxxxxxxx.s1.eu.hivemq.cloud";
// const int MQTT_PORT = 8883;
// const char* MQTT_USER = "your_username";
// const char* MQTT_PASS = "your_password";
*/
// ======= トピック設定 =======
// 購読(Subscribe)するトピック。PCから送られてくるデータを受け取る宛先です。
// "m5class/roomA/+/led" の "+" はワイルドカードで、誰宛のデータでも受け取る設定です。
// 特定の人(自分など)だけ受信したい場合は "m5class/roomA/tsuchida/led" のように指定します。
// const char* MQTT_SUB = "m5class/roomA/+/led";
const char* MQTT_SUB = "m5class/roomA/tsuchida/led";
// 自分の接続状態を通知するためのトピック(生存確認用)
// "status" というサブトピックに "online" や "offline" を書き込みます。
const char* TOPIC_STAT = "m5class/roomA/tsuchida/status";
// ======= NeoPixel (LED) 設定 =======
// LEDテープを接続しているピン番号(G32など、配線に合わせて変更してください)
#define LED_PIN 32
// 接続しているLEDの個数
#define LED_COUNT 15
// NeoPixelオブジェクトを作成します(個数, ピン番号,色の並び順+通信速度)
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// ======= MQTTクライアント準備 =======
// TLS(暗号化)通信を行うためのクライアント機能を作成
WiFiClientSecure tlsClient;
// そのクライアントを使ってMQTT通信を行う機能を作成
PubSubClient mqtt(tlsClient);
// 画面の表示更新タイミングを管理するための変数
uint32_t lastUiMs = 0;
// -------- データ受信時の処理 --------
// MQTTでメッセージが届いた時に自動的に呼び出される関数(コールバック関数)
// topic: 届いたトピック名, payload: データの中身(バイト列), len: データの長さ
void onMqtt(char* topic, byte* payload, unsigned int len) {
// 受信したデータを扱いやすい「文字列」に変換するための準備
static char buf[64];
// データが長すぎてバッファから溢れないように長さを制限します
len = (len < sizeof(buf) - 1) ? len : sizeof(buf) - 1;
// payloadの中身をbufにコピーします
memcpy(buf, payload, len);
// 文字列の最後には必ず「終端文字(\0)」を付けるルールがあります
buf[len] = '\0';
// 受信した内容をM5の画面下部に表示して確認できるようにします
M5.Display.setTextColor(TFT_WHITE, TFT_BLACK); // 白文字、背景黒
M5.Display.setCursor(8, 200); // 表示位置を指定
M5.Display.printf("RX %s : %s \n", topic, buf); // トピックとデータを表示
// 期待しているデータの形式は "R,G,B"(例: "255,0,100")です
int r = 0, g = 0, b = 0;
// sscanf関数を使って、文字列から3つの数字を取り出します
// 戻り値が3なら、正しく3つの数字が読み取れたということです
if (sscanf(buf, "%d,%d,%d", &r, &g, &b) == 3) {
// 数字が0〜255の範囲に収まるように調整します(異常値対策)
r = constrain(r, 0, 255);
g = constrain(g, 0, 255);
b = constrain(b, 0, 255);
// NeoPixel用に色のデータを作成します
uint32_t color = strip.Color(r, g, b);
// ★ガンマ補正:人間の目にとって自然な明るさに変換します
color = strip.gamma32(color);
// 全てのLEDに対して同じ色を設定します
for (int i = 0; i < LED_COUNT; ++i) {
strip.setPixelColor(i, color);
}
// 設定した色を反映させて実際に光らせます
strip.show();
}
}
// -------- MQTT接続確認・再接続処理 --------
// 定期的に呼び出し、接続が切れていたら再接続を試みます
void ensureMqtt() {
// すでに接続されているなら何もしないで戻ります
if (mqtt.connected()) return;
// 画面に「接続中…」と表示します
M5.Display.setTextColor(TFT_YELLOW, TFT_BLACK); // 黄色文字
M5.Display.setCursor(8, 100);
M5.Display.println("MQTT: connecting...");
// クライアントIDをランダムに生成します
// 同じIDを持つ機器が複数あると、接続が弾かれてしまうためです
String cid = String("m5-") + String((uint32_t)esp_random(), HEX);
// LWT(遺言)機能の設定:
// もし電源が急に切れるなどして通信が途絶えたら、サーバーが自動でこのメッセージを書き込みます
const char* willTopic = TOPIC_STAT; // 書き込む場所
const char* willMessage = "offline"; // 書き込む内容
const bool willRetain = true; // 後から来た人にも知らせるか
const int willQos = 0; // 通信品質設定
// サーバーへの接続を試みます
// ユーザー名(MQTT_USER)とパスワード(MQTT_PASS)はNULLの場合、認証なしで接続します
bool ok = mqtt.connect(
cid.c_str(), // クライアントID
MQTT_USER, MQTT_PASS, // ユーザー名とパスワード(今回はNULL)
willTopic, willQos, willRetain, willMessage // LWT設定
);
if (ok) {
// 接続に成功した場合
// 指定したトピックを購読(受信待ち)開始します
mqtt.subscribe(MQTT_SUB);
// 自分のステータスを「online」にして送信します
mqtt.publish(TOPIC_STAT, "online", true);
// 画面に「接続成功」と緑色で表示します
M5.Display.setTextColor(TFT_GREEN, TFT_BLACK);
M5.Display.setCursor(8, 120);
M5.Display.println("MQTT: connected");
} else {
// 接続に失敗した場合
// 画面に「失敗」と赤色で表示し、エラーコード(rc)も出します
M5.Display.setTextColor(TFT_RED, TFT_BLACK);
M5.Display.setCursor(8, 120);
M5.Display.printf("MQTT NG, rc=%d\n", mqtt.state());
// すぐ再試行すると負荷がかかるので、1秒待ちます
delay(1000);
}
}
// -------- 初期設定 (一度だけ実行) --------
void setup() {
// ---- M5本体の初期化 ----
auto cfg = M5.config(); // 設定情報を取得
M5.begin(cfg); // M5デバイスを開始
M5.Display.setRotation(1); // 画面の向きを横長(1)に設定
M5.Display.fillScreen(TFT_BLACK); // 画面全体を黒で塗りつぶし
M5.Display.setTextSize(1); // 文字サイズを標準に設定
M5.Display.setTextColor(TFT_CYAN, TFT_BLACK); // 水色文字
M5.Display.setCursor(8, 8); // 左上にカーソル移動
M5.Display.println("MQTT TLS + NeoPixel"); // タイトル表示
M5.Display.setTextColor(TFT_WHITE, TFT_BLACK); // 白文字に戻す
// ---- NeoPixel(LED)の初期化 ----
strip.begin(); // LED制御を開始
strip.setBrightness(30); // 明るさを設定 (0〜255)。30くらいが眩しすぎず適当です
strip.clear(); // 色データをリセット
strip.show(); // 一旦消灯させる
// ---- Wi-Fi接続 ----
M5.Display.setCursor(8, 40);
M5.Display.printf("Wi-Fi: %s\n", WIFI_SSID); // SSIDを表示
WiFi.mode(WIFI_STA); // 子機モードに設定
WiFi.begin(WIFI_SSID, WIFI_PASS); // 接続開始
// 接続待ちのタイムアウト計測用
uint32_t t0 = millis();
// つながるまでループして待機
while (WiFi.status() != WL_CONNECTED) {
delay(300); // 0.3秒待つ
M5.Display.print("."); // 進捗を示すドットを表示
// もし20秒経っても繋がらなければ
if (millis() - t0 > 20000) {
M5.Display.println("\nWi-Fi timeout. Reboot.");
delay(800);
esp_restart(); // 強制再起動(授業などでの復帰用)
}
}
// 接続成功したらIPアドレスを表示
M5.Display.setCursor(8, 70);
M5.Display.printf("IP: %s\n", WiFi.localIP().toString().c_str());
// ---- TLS (SSL) 設定 ----
// test.mosquitto.org の 8883番ポートは暗号化されていますが、
// 簡単のため証明書の検証をスキップする設定にします
tlsClient.setInsecure();
// ---- MQTTクライアント設定 ----
mqtt.setServer(MQTT_HOST, MQTT_PORT); // 接続先サーバーとポートを設定
mqtt.setCallback(onMqtt); // 受信時に呼ぶ関数を登録
mqtt.setBufferSize(512); // 通信バッファサイズを確保
mqtt.setKeepAlive(30); // 接続維持確認の間隔(秒)
mqtt.setSocketTimeout(10); // 通信タイムアウト設定(秒)
// 最初の接続を試みます
ensureMqtt();
}
// -------- メインループ (繰り返し実行) --------
void loop() {
M5.update(); // ボタン等のハードウェア状態を更新
ensureMqtt(); // MQTT接続が切れていないか確認し、切れていれば再接続
mqtt.loop(); // MQTTの送受信処理を実行(これを呼ばないと通信できません)
// 画面情報の更新(1秒に1回だけ行う)
uint32_t now = millis();
if (now - lastUiMs >= 1000) {
lastUiMs = now;
// Wi-Fiの電波強度(RSSI)を表示
M5.Display.setCursor(8, 130);
M5.Display.printf("WiFi %s RSSI %d dBm \n",
(WiFi.status() == WL_CONNECTED ? "OK" : "NG"), WiFi.RSSI());
// MQTTの接続状態とエラーコードを表示
M5.Display.setCursor(8, 150);
M5.Display.printf("MQTT %s (state %d) \n",
(mqtt.connected() ? "OK" : "NG"), mqtt.state());
}
// CPUを休ませるためのごく短い待機(省電力・安定動作のため)
delay(1);
}
※LEDのDINピンはG32に刺さるように設定しています。
// M5StickC Plus2 の Grove 端子に接続した想定の信号ピン(手元の配線に合わせて変更可)
#define LED_PIN 32
※ tsuchidaのところは自分の名前に。一斉に光らせる場合にはワイルドカード「+」を選択。
コード解説

setup() 関数
M5が起動したときに一度だけ実行されます。
M5.begin():- M5本体の初期設定をします。画面表示やボタンなどを使えるようにします。
- M5本体の初期設定をします。画面表示やボタンなどを使えるようにします。
WiFi.begin():- Wi-Fiに接続します。
tlsClient.setInsecure():- 今回接続する
test.mosquitto.orgのポート8883は暗号化通信(TLS)を行いますが、証明書の検証を簡略化するために使用します。
- 今回接続する
mqtt.setCallback(onMqtt):- 「
onMqttという名前の関数を、メッセージが届くたびに呼び出してね」とMQTTクライアントに教えています。
- 「
tlsClient.setInsecure() の役割と注意点(時間がある際に読んでみてください)
このプログラムでは、MQTTサーバーとの安全な通信のために「TLS(Transport Layer Security)」という仕組みを使っています。 このTLS通信を正しく行うには、本来、接続先のサーバーが「信頼できる」ことを証明するための「証明書」というものが必要になります。しかし、この確認作業は少し複雑なため、授業でスムーズに通信を試すにはハードルとなります。
- この命令がやっていること
tlsClient.setInsecure()は、「証明書の検証を一時的にスキップする」という命令です。 例えるなら、「この荷物は〇〇さんからのものだと、身分証明書で確認する必要があるけど、今回は時間が無いから、身分証明書を見なくても受け取ってしまおう!」という状態です。これを使うことで、複雑な設定なしに暗号化通信(MQTTS)を確立できます。
- なぜ「製品開発では使わない」のか
- この命令は、あくまで実験やデモのために用意されています。証明書の検証をスキップすると、悪意のあるサーバーが本物のサーバーになりすましていた場合に気づけないリスクがあります。 今回の授業では公開データ(RGB値)をやり取りするだけなので問題ありませんが、将来パスワードや個人情報を扱う作品を作る場合は、この命令は使わず、正しく証明書を設定(
setCACert等を使用)する必要があります。
- この命令は、あくまで実験やデモのために用意されています。証明書の検証をスキップすると、悪意のあるサーバーが本物のサーバーになりすましていた場合に気づけないリスクがあります。 今回の授業では公開データ(RGB値)をやり取りするだけなので問題ありませんが、将来パスワードや個人情報を扱う作品を作る場合は、この命令は使わず、正しく証明書を設定(
onMqtt() 関数
この関数は、MQTTでメッセージが届くと自動的に呼び出されます。
sscanf(buf, "%d,%d,%d", &r, &g, &b):- 届いた文字列(buf)から、RGBの3つの数字を読み取ります。
- 届いた文字列(buf)から、RGBの3つの数字を読み取ります。
r = constrain(r, 0, 255):- RGBの値は0から255の範囲でなければならないので、もし範囲外の値が届いたら、自動的に正しい範囲に収めるようにしています。
- RGBの値は0から255の範囲でなければならないので、もし範囲外の値が届いたら、自動的に正しい範囲に収めるようにしています。
strip.setPixelColor(i, color):- NeoPixelに「何番目のLEDを、何色で光らせるか」を指示します。
- NeoPixelに「何番目のLEDを、何色で光らせるか」を指示します。
strip.show():- 設定した色で、実際にLEDを光らせます。
loop() 関数
この関数は、M5が動いている間、ずっと繰り返し実行されます。
ensureMqtt():- MQTTの接続が切れていないか常に確認し、もし切れていたら自動的につなぎ直します。
- MQTTの接続が切れていないか常に確認し、もし切れていたら自動的につなぎ直します。
mqtt.loop():- MQTT通信をスムーズに行うための、とても大事な処理です。この命令を常に実行することで、メッセージの受信や接続の維持が行われます。
- MQTT通信をスムーズに行うための、とても大事な処理です。この命令を常に実行することで、メッセージの受信や接続の維持が行われます。
動作確認
このコードをM5StickC Plus2に書き込むと、PCから送られてくる動画の色情報に合わせて、LEDが光り始めるはずです。動画とシンクロしているはずです。

全員がトピック名をワイルドカード「+」に設定した場合、全員のLEDの明滅がシンクロするはずです。サーバーの性能や通信環境、通信量や通信頻度にもよりますが、数百台から数千台、それ以上のデバイスの同期が可能なはずです(試したことないので試したいです)。
