- 概要
- PC[動画(p5.js)→色情報(RGB)]→MQTT→M5→LED
- PC[動画(p5.js)→色情報(RGB)]-UDP→M5→LED
- TCP (MQTT) と UDP の違い
- Step5 PC側
- Step5-1 PC側:中継サーバーの準備 (Node.js) ← 今ココ
- Step5-2 PC側:p5.js (sketch.js)
- Step6 M5側:UDP受信 (step06_led_udp.ino)
- 課題
目次

Step5-1 PC側:中継サーバーの準備 (Node.js)
フォルダとライブラリの準備
ターミナル(WindowsならPowerShellやコマンドプロンプト、macOSならターミナル)を開きます。
以下のコマンドを順番に実行してください。
mkdir step05_video_color_udp_sync
cd step05_video_color_udp_sync
npm init -y
npm i ws
bridge.js の作成
VSCodeを使って step05_video_color_udp_sync フォルダ内に bridge.js というファイルを作成し、以下のコードをコピー&ペーストします。
bridge.js
/**
* UDP Bridge Server (Node.js)
* 役割: p5.js (WebSocket) と M5StickC (UDP) の間の通訳
* 理由: ブラウザは直接UDP通信ができないため、このNode.jsアプリが仲介します。
*/
// --- 1. 必要なライブラリの読み込み ---
const WebSocket = require('ws'); // WebSocket通信用
const dgram = require('dgram'); // UDP通信用
// --- 2. 通信設定(定数) ---
const WS_PORT = 8080; // p5.js (ブラウザ) からの接続を受け付けるポート
const UDP_PORT = 8000; // M5StickC (マイコン) へデータを送信する先のポート
// ========== 送信先の設定(どちらか片方を選んでください) ==========
// 【パターンA】 全員に送る(ブロードキャスト) ※現在はこっちが有効
// '255.255.255.255' を指定すると、同じWi-Fi内の全デバイスに届きます
const UDP_HOST = '255.255.255.255';
// 【パターンB】 特定の1台だけに送る(ユニキャスト)
// ※ 特定の相手に送る場合は、上のパターンAをコメントアウト(//)し、
// 下のコメント(//)を外して、M5の画面に表示されたIPアドレスを書いてください。
// const UDP_HOST = '192.168.11.5'; // 例: M5の画面に出ているIP
// =============================================================
// --- 3. UDPクライアント(送信機)の準備 ---
// IPv4形式のUDPソケット(通信の出入り口)を作成します
const udpClient = dgram.createSocket('udp4');
// ソケットをシステムにバインド(紐付け)して準備完了状態にします
udpClient.bind(() => {
// ブロードキャスト(一斉送信)機能を有効にします
// ※パターンB(特定送信)の場合でも、この行はそのままで問題ありません
udpClient.setBroadcast(true);
});
// --- 4. WebSocketサーバー(受信機)の準備 ---
// 指定したポートでWebSocketサーバーを立ち上げます
const wss = new WebSocket.Server({ port: WS_PORT });
// サーバーが起動したことをターミナルに表示します
console.log(`Bridge running... WS:${WS_PORT} -> UDP:${UDP_HOST}:${UDP_PORT}`);
// --- 5. 通信のメイン処理 ---
// ブラウザ(p5.js)から接続があったときに実行されるイベント
wss.on('connection', ws => {
console.log('p5.js connected!');
// ブラウザからメッセージ(色データなど)が届いたときに実行されるイベント
ws.on('message', message => {
// ★デバッグ用:届いたデータをターミナルに表示!
// これが表示されなければ、p5.js側がおかしいです
console.log(`RX: ${message}`);
// 受け取ったメッセージをUDPで送れる「Buffer(バイト列)」に変換します
const msgBuffer = Buffer.from(message.toString());
// 変換したデータをUDPを使って送信します
udpClient.send(msgBuffer, 0, msgBuffer.length, UDP_PORT, UDP_HOST, (err) => {
if (err) console.error(err);
});
});
});
// --- 6. 終了処理(お行儀よく終わる設定) ---
// ターミナルで「Ctrl + C」が押されたときを検知します
process.on('SIGINT', () => {
console.log('\n[SYS] SIGINT: closing...');
udpClient.close(); // UDPソケットを閉じる
wss.close(() => process.exit(0)); // WebSocketサーバーを閉じて終了
});
コード解説
require('ws'):- インストールしたWebSocketライブラリを使えるように読み込んでいます。
- インストールしたWebSocketライブラリを使えるように読み込んでいます。
dgram.createSocket('udp4'):- UDP通信を行うための「ソケット(差込口)」を作成します。
- UDP通信を行うための「ソケット(差込口)」を作成します。
udpClient.setBroadcast(true):- これが重要です。特定のIPアドレス(相手)を指定するのではなく、ネットワーク内の全員にデータをばら撒く「ブロードキャスト送信」を許可しています。
- これが重要です。特定のIPアドレス(相手)を指定するのではなく、ネットワーク内の全員にデータをばら撒く「ブロードキャスト送信」を許可しています。
ws.on('message', ...):- ブラウザ(p5.js)からデータが届くたびに実行されます。ここで「受け取ったメッセージ」を即座に「UDPパケット」として転送しています。
- ブラウザ(p5.js)からデータが届くたびに実行されます。ここで「受け取ったメッセージ」を即座に「UDPパケット」として転送しています。
process.on('SIGINT', ...):- プログラムを終了する際(Ctrl+Cを押した際)に、通信ポートを開きっぱなしにしないよう、行儀よく閉じてから終了する処理です。
- プログラムを終了する際(Ctrl+Cを押した際)に、通信ポートを開きっぱなしにしないよう、行儀よく閉じてから終了する処理です。
起動方法
ターミナルで以下のコマンドを実行します。
node bridge.js
以下のように表示されれば準備完了です。
Bridge running... WS:8080 -> UDP:255.255.255.255:8000
※ このターミナルは開いたままにしておいてください。
※ 停止したいときは、ターミナルをクリックしてから Ctrl + C を押します。
Step5-2 PC側:p5.js (sketch.js)
変更のポイント
- ライブラリ削除: WebSocket は現代のWebブラウザに標準搭載されている機能なので、MQTTの時のような外部ライブラリの読み込みは不要です。
- 宛先の変更: インターネット上の
test.mosquitto.orgではなく、自分のPC内で動いているlocalhost(中継サーバー)に接続します。
index.html の修正
まず、不要になったMQTTライブラリを削除します。index.html を開き、MQTTライブラリを読み込む以下の一行を削除します。
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
index.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>UDP</title>
<script src="https://cdn.jsdelivr.net/npm/p5@1.10.0/lib/p5.min.js"></script>
<style>
body {
margin: 0;
font: 14px/1.4 system-ui;
overflow: hidden;
}
#ui {
position: fixed;
left: 8px;
top: 8px;
background: #0008;
color: #fff;
padding: 8px;
border-radius: 8px;
}
</style>
</head>
<body>
<div id="ui">
status: <span id="st">connecting...</span><br>
RGB: <span id="rgb">-</span>
</div>
<script src="./sketch.js"></script>
</body>
</html>
sketch.js
sketch.js
// グローバル変数
let vid;
let socket; // WebSocketクライアント
let statusText;
let rgbText;
// 中継サーバーのURL(自分自身のPC上の8080ポート)
const wsUrl = 'ws://localhost:8080';
function preload(){
// 動画ファイルの読み込み
vid = createVideo("clip.mp4");
}
function setup(){
// ウィンドウ全体のサイズでキャンバスを作成
createCanvas(windowWidth, windowHeight);
// 動画のHTML要素は隠す
vid.hide();
// 繰り返し再生
vid.loop();
statusText = document.getElementById("st");
rgbText = document.getElementById("rgb");
// === WebSocket接続 ===
// ブラウザ標準の機能で接続します
socket = new WebSocket(wsUrl);
// 接続が開いたとき
socket.onopen = () => {
console.log("WS Connected");
statusText.innerHTML = "WS Connected";
};
// エラー時
socket.onerror = (error) => {
console.log("WS Error", error);
statusText.innerHTML = "WS Error (Run bridge.js!)";
};
}
// ウィンドウサイズが変更されたときにキャンバスもリサイズする
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
function draw(){
background(20);
// === 画面いっぱいに動画を表示する計算 (Coverモード) ===
let vidRatio = vid.width / vid.height;
let canvasRatio = width / height;
let w, h;
if (canvasRatio > vidRatio) {
// 画面の方が横長なら、幅を合わせる(高さがはみ出る)
w = width;
h = w / vidRatio;
} else {
// 画面の方が縦長なら、高さを合わせる(幅がはみ出る)
h = height;
w = h * vidRatio;
}
// 中心に配置
let x = (width - w) / 2;
let y = (height - h) / 2;
// 動画を描画
image(vid, x, y, w, h);
// === 色情報の取得と送信 ===
// 画面中央の色を取得
let c = get(width / 2, height / 2);
rgbText.innerHTML = `${c[0]}, ${c[1]}, ${c[2]}`;
// プレビュー描画(右下に固定)
noStroke();
fill(c[0], c[1], c[2]);
rect(width - 60, height - 60, 50, 50);
// WebSocketがつながっているなら送信
if (socket.readyState === WebSocket.OPEN) {
// RGB値をカンマ区切りの文字列に変換
const message = `${c[0]},${c[1]},${c[2]}`;
socket.send(message);
}
}
コード解説:p5.js (Full Screen + WebSocket)
このコードは、これまでの「固定サイズのキャンバス × MQTT」から、「全画面表示のキャンバス × WebSocket」 へと進化しています。主な変更点と仕組みを解説します。
1. 画面いっぱいに映像を広げる(Coverモード)
これまでのコードでは、画面サイズと動画サイズが合わないと「黒い余白(黒帯)」ができていました。 今回は、「画面を隙間なく埋め尽くす(Cover)」 計算式を導入しています。
- 仕組み: 動画と画面、それぞれの「縦横比(アスペクト比)」を計算します。
- 画面の方が「横長」なら: 動画の幅を画面に合わせます(動画の上下が少しはみ出します)。
- 画面の方が「縦長」なら: 動画の高さを画面に合わせます(動画の左右が少しはみ出します)。
これにより、ブラウザのサイズを自由に変えても、大きな映像で体験できます。

2. 通信方式の変更(MQTT → WebSocket)
この sketch.js は、M5StickC Plus2へ直接データを送るのではなく、自分のパソコン内で動いている「中継サーバー (bridge.js)」にデータを渡す役割を持っています。
PC内部での通信なので、インターネットを経由するMQTTではなく、ブラウザ標準の「WebSocket」 を使用しています。
new WebSocket('ws://localhost:8080'):localhostは「自分自身のPC」を指します。8080は中継サーバーが待ち構えているポート番号です。
socket.send(message):- トピック(宛先名)の指定などは不要です。パイプで繋がった中継サーバーへ、データを流し込むだけでOKです。
- トピック(宛先名)の指定などは不要です。パイプで繋がった中継サーバーへ、データを流し込むだけでOKです。
コードの各部詳細
// ウィンドウサイズが変わった時に自動で実行される関数
function windowResized() {
// キャンバスの大きさを新しいウィンドウサイズに合わせる
resizeCanvas(windowWidth, windowHeight);
}
function draw() {
// ...(中略)...
// === 画面埋め尽くし計算 ===
// vidRatio: 動画の比率, canvasRatio: 画面の比率
if (canvasRatio > vidRatio) {
// 画面の方が横長 → 幅を基準にする
w = width;
h = w / vidRatio;
} else {
// 画面の方が縦長 → 高さを基準にする
h = height;
w = h * vidRatio;
}
// ...(中略)...
// WebSocketが開通している時だけデータを送信
if (socket.readyState === WebSocket.OPEN) {
socket.send(message);
}
}