目次
このページのゴール
「M5StickC Plus2をWi-Fiに繋いでHTTPサーバ化し、加速度センサのデータをp5.jsで可視化、ブラウザクリックでM5画面色を切り替えられるようにすること」

今日のねらい
- 文化情報工学基礎演習では有線接続でシリアル通信でPCとArduino Nanoを連動させましたが、文化情報デザイン工学では無線制御を行います。
 - M5StickC Plus2 をWi‑Fiに接続し、
- HTTPサーバとして 
/pingと/sensorsを公開、 - p5.js(ブラウザ)から 
fetch()でセンサー値を取得して円が動く可視化、 - クリックで M5StoclC Plus2 の画面色を赤/黒に切り替え(HTTP 制御)。
 
 - HTTPサーバとして 
 - 完成時に見えるもの
- M5 の画面に IPアドレス(例: 
192.168.1.23) - ブラウザで 
/pingにアクセスすると pong - p5 のキャンバス上で 円が傾きに合わせて動く
 - クリックすると M5 の画面色が 赤↔黒 に変化
 
 - M5 の画面に IPアドレス(例: 
 
Webの基本的なやり取り
私たちが普段スマートフォンやPCでWebサイトを見るとき、Webブラウザ(クライアント)が、Webサイトのデータを持っているWebサーバに対して「このページをください」と要求(リクエスト)を送り、サーバが「はい、どうぞ」と応答(レスポンス)を返す、というやり取りが行われています。
この「クライアントとサーバ間でのやり取りのルール」を定めたプロトコル(通信規約)が HTTP (HyperText Transfer Protocol) です。
今回の演習では、PCのWebブラウザ(p5.jsが動く場所)がクライアント、M5StickC Plus2がサーバの役割を担います。M5StickCがHTTPサーバとして振る舞うことで、ブラウザからの要求(リクエスト)に応答(レスポンス)できるようになります。

1. 準備物
- M5StickC Plus2 本体
 - USB-C データ通信対応ケーブル
 - 2.4GHz の Wi‑Fi(5GHz だけのSSIDは不可)
 - PC(Windows/Mac/Linux)
 - Arduino IDE
 - VSCode + Live Server (#3で学習)
 
電源メモ:うまく点かないときは電源ボタン長押しで強制OFF→再度長押しでON。
2. Wi‑Fi 接続 & IP 表示
まず M5StickC Plus2 を Wi-Fi に接続し、割り当てられた IP アドレスを画面に表示させます。
この IP アドレスがわかると、後のステップで「PCのブラウザ → M5StickC Plus2」とアクセスできるようになります。
ネットワーク条件:PC と M5 が同じネットワーク(同じLAN/同じWi-Fi)にいること。
手順
- Arduino IDE で 新規スケッチを作成し、下記を丸ごと貼り付け。
 ssidとpassを自分の Wi-Fi のものに書き換える。- M5StickC Plus2 に書き込む。
 
コード(step02_wifi_ip.ino)
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ
#include <WiFi.h>        // Wi-Fi 接続を行うための標準ライブラリ
// 接続先のWi-Fi情報をここに書く
const char* ssid = "YOUR-SSID";   // ←自分のWi-FiのSSIDに変更
const char* pass = "YOUR-PASS";   // ←自分のWi-Fiのパスワードに変更
void setup(){
  auto cfg = M5.config();    // M5の設定を取得
  M5.begin(cfg);             // M5StickC Plus2 を初期化
  // 画面の設定(向き・文字サイズなど)
  M5.Display.setRotation(1);     // 画面を横向きにする
  M5.Display.fillScreen(TFT_BLACK); // 画面を黒でクリア
  M5.Display.setTextSize(2);     // 文字サイズを大きめに
  M5.Display.setCursor(8,8);     // 表示開始位置を左上に
  M5.Display.println("Connecting WiFi..."); // Wi-Fi接続中と表示
  // Wi-Fiに接続を開始
  WiFi.begin(ssid, pass);
  // 接続が完了するまで待つ(状態が「接続済み」になるまでループ)
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);  // 0.3秒待つ(接続が安定するまで繰り返す)
  }
  // 接続できたら画面にIPアドレスを表示
  M5.Display.fillScreen(TFT_BLACK);          // 画面をクリア
  M5.Display.setCursor(8,8);                 // 表示位置を左上に
  M5.Display.printf("IP: %s\n", WiFi.localIP().toString().c_str()); 
  // Wi-Fiから取得したローカルIPアドレスを文字列にして表示
  Serial.begin(115200); // PCとのシリアル通信開始
  Serial.printf("IP: %s\n", WiFi.localIP().toString().c_str()); 
  // シリアルモニタにも同じIPを出力(確認用)
  M5.Display.printf("step2");
}
void loop(){ 
  M5.delay(10); // ほんの少し待機(CPUを休ませる)
}
確認方法
- M5 の画面に 
IP: 192.168.x.xのように出れば成功です。
 - PC のターミナル/コマンドプロンプトなどで 
ping 192.168.x.xを打って応答があれば通信OK。(PCも同じWifiに接続する必要があります)- Mac
- OK

 - NG

 - macOS / Linux は Ctrl+C を押すまで永遠に続く。
 
 - OK
 - Windows
-  
pingはデフォルトで4回だけ実行して止まる。 
 -  
 
 - Mac
 
うまくいかない場合
pingが通らない → 同じLANにいない可能性。- とりあえずの解決策:
- もしかするとルーターに接続する人数が多すぎてエラーが出るかもしれません。
- スマホのテザリングに PC と M5 を同時接続すると安定するかもです。
 
 
 - もしかするとルーターに接続する人数が多すぎてエラーが出るかもしれません。
 
このステップは「M5StickC Plus2 をネットワークの仲間に入れる」作業です。
次のステップから、そのIPを使って HTTPサーバを立て、ブラウザからアクセスできるようにします。
3. HTTP サーバ
前のステップ(2. Wi-Fi 接続 & IP 表示)では、M5StickC Plus2 が ネットワークに参加して住所(IPアドレス)がわかる ところまででした。この時点で ping コマンドを使うと応答が返ってきます。これは ESP32が持つ基本機能(ICMPエコー応答)で、OSレベルで自動的に対応しているためです。
しかし ブラウザが使うのは HTTP という別の通信方法なので、そのままではアクセスしても反応しません。そこでここからは、M5StickC Plus2 に HTTPサーバ機能を実装し、リクエストに対して「pong」と返せるようにします。
最初の練習として、http://<M5のIP>/ping にアクセスすると「pong」と返す、非常にシンプルなHTTPサーバを作ります。
この「応答できる」状態になって、初めて「PCのブラウザ → M5StickC Plus2」での通信を確認できます。

手順
- 先ほどのコードに HTTP サーバ機能を追加します。
 - 下のコードを丸ごと貼り替え → 書き込み。
※ ssidとpassの書き換え忘れずに! - ブラウザで 
http://<M5のIP>/pingを開く。 
M5側
コード(step03_http_ping.ino)
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ
#include <WiFi.h>        // Wi-Fi接続に必要なライブラリ
#include <WebServer.h>   // HTTPサーバを動かすライブラリ
const char* ssid = "YOUR-SSID";    // 接続するWi-FiのSSID(要変更)
const char* pass = "YOUR-PASS";    // 接続するWi-Fiのパスワード(要変更)
WebServer server(80);             // ポート80でHTTPサーバを用意
// CORS対応のためのヘッダを付与する関数
void cors(){
  server.sendHeader("Access-Control-Allow-Origin", "*");           // すべてのアクセス元を許可
  server.sendHeader("Access-Control-Allow-Methods", "GET,OPTIONS");// GETとOPTIONSを許可
  server.sendHeader("Access-Control-Allow-Headers", "Content-Type");// Content-Typeヘッダを許可
}
void setup(){
  auto cfg = M5.config(); M5.begin(cfg);       // M5を初期化
  M5.Display.setRotation(1);                   // 画面を横向きに設定
  M5.Display.setTextSize(2);                   // 文字サイズを大きめに設定
  M5.Display.fillScreen(TFT_BLACK);            // 画面を黒でクリア
  WiFi.begin(ssid, pass);                      // Wi-Fi接続開始
  while (WiFi.status() != WL_CONNECTED) {      // 接続が完了するまで待機
    delay(300);                                // 0.3秒ごとに確認
  }
  // 「/ping」にアクセスがあったら「pong」と返す処理を登録
  server.on("/ping", HTTP_GET, [](){ 
    cors();                                    // CORSヘッダを追加
    server.send(200, "text/plain", "pong");    // ステータス200で"pong"を返す
  });
  // 存在しないパスにアクセスされた場合の処理
  server.onNotFound([](){
    if (server.method()==HTTP_OPTIONS){        // OPTIONSメソッドなら
      cors(); server.send(204);                // CORS対応して204(No Content)を返す
    }
    else {                                     // それ以外は
      server.send(404, "text/plain", "Not Found"); // 404エラーを返す
    }
  });
  server.begin();                              // HTTPサーバを開始
  M5.Display.setCursor(8,8);                   // 文字表示位置を左上に
  // M5画面に自分のIPアドレスを表示
  M5.Display.printf("IP: %s\n", WiFi.localIP().toString().c_str());
  M5.Display.printf("step3");
}
void loop(){ 
  server.handleClient();                       // クライアントからのアクセスを処理
}
確認:ブラウザに pong と表示されれば成功。(私の場合はhttp://192.168.68.56/ping にアクセス。)

解説
server.on("/ping", HTTP_GET, … )
→ URL/pingに GETリクエストが来たときの処理を指定。[](){ … }
→ 無名関数(ラムダ式)。アクセスされたときに中の処理が実行される。cors();
→Access-Control-Allow-Origin: *を返す設定。
これがないと、ブラウザのfetch("http://.../ping")がCORSエラーで止まる。server.send(200, "text/plain", "pong");
→ クライアント(ブラウザやcurl)に- 200 = OK 成功
 - text/plain = 普通の文字列です
 - pong = 中身のテキスト
を返す。 
【補足】 リクエストとレスポンスの中身

ブラウザ(クライアント)がサーバに送る「要求」を HTTPリクエスト(今回だとpingにあたる)、サーバがブラウザに返す「応答」を HTTPレスポンス(今回だとpongにあたる)と呼びます。
1. HTTPリクエスト (要求) 
 リクエストには、「何をしてほしいか」を示すメソッドが含まれます。
HTTP_GET(GETメソッド): コードに出てきたHTTP_GETは、最も基本的なメソッドで、「指定した情報(データやページ)をください」という意味を持ちます。 ブラウザのアドレスバーにURL(http://<M5のIP>/ping)を入力してエンターを押すと、このGETリクエストがM5(サーバ)に送信されます。
(参考)HTTPメソッド一覧表
| メソッド名 | 主な用途 | 内容/説明 | 具体例・備考 | 
|---|---|---|---|
| HEAD | ヘッダ情報だけ取得 | HTTPヘッダのみを取得し、レスポンスボディ(中身)は受け取らない。通信確認や更新日時の確認に使う。 | 例:更新チェック(Last-Modified ヘッダ) | 
| GET | データの取得 | Webサーバから情報を取得する。URLにパラメータを付与して送信する(例:?name=test&pass=123)。 | 主に閲覧・検索・取得に使用。ブラウザのURL入力やリンククリックもGET。 | 
| POST | データの送信 | フォームの内容などをWebサーバに送信する。送信データはURLに含まれず、HTTP本文(body)に格納。 | 例:ログインフォーム、問い合わせ送信など。セキュリティが高い。 | 
| PUT | データの置換 | 指定したリソース全体を新しいデータで置き換える。Web APIなどで利用される。 | 例:既存データを新しい内容で更新。 | 
| DELETE | データの削除 | 指定したリソースを削除する。Web APIなどで利用。 | 例:ファイル削除、データ削除要求。 | 
| CONNECT | プロキシ接続 | トンネル接続を開始する。主にHTTPS通信で利用。 | 例:ブラウザ→プロキシ→サーバの中継。 | 
| OPTIONS | 利用可能メソッド確認 | サーバがサポートしているHTTPメソッドを問い合わせる。 | 例:CORS(クロスオリジン通信)の事前確認で使用。 | 
| TRACE | ループバックテスト | クライアントからのリクエスト内容をそのまま返す。通信経路のデバッグに使用。 | 現在はセキュリティ上の理由でほとんど無効化されている。 | 
GET と POST の違い(データ送信)
| 比較項目 | GETメソッド | POSTメソッド | 
|---|---|---|
| 送信方法 | URLの後に?キー=値形式で付ける | HTTPメッセージ本文(body)に埋め込む | 
| データ表示 | URLに見える(ブラウザのアドレスバーに表示される) | URLには表示されない(非表示) | 
| データ長 | 制限あり(URL長制限) | 制限なし(大量データOK) | 
| 用途 | 情報の取得、検索 | 情報の送信、登録、更新 | 
| 安全性 | △(URLにデータが残る) | ○(データが見えにくい) | 
| キャッシュ | される可能性あり | 基本的にされない | 
| 例 | /login.html?name=test&pass=123 | /login.html(bodyにname=test&pass=123を送信) | 
まとめポイント
HEAD/OPTIONS → 通信の確認やメタ情報チェック
GET → 情報を「取得」する(読むだけ)
POST → 情報を「送信」する(書き込む)
PUT/DELETE → APIなどでデータを「更新」「削除」
2. HTTPレスポンス (応答) 
 M5(サーバ)がブラウザに返すレスポンスは、大きく3つの部分で構成されています。
server.send(200, "text/plain", "pong");
この1行は、以下の3つの情報をまとめて送る指示です。
- ① ステータス行 (200)
200: これは「ステータスコード」と呼ばれる番号です。200は「OK (成功)」、つまりリクエストが正常に処理されたことを意味します。- 有名なエラーコードには 
404 Not Found(ページが見つからない) 、503 Service Unavailable(サーバが応答できない)などがあります。 
 - ② HTTPヘッダ (“text/plain”)
"text/plain": これは Content-Type (コンテントタイプ) というヘッダ情報です。- ヘッダとは、これから送るデータに関する「付箋」や「補足情報」のようなものです。
 Content-Type: text/plainは、「これから送るデータの中身(ボディ)は、普通の文字列ですよ」とブラウザに伝える役割を持ちます。他にも application/json など色々あります。
 - ③ メッセージボディ (“pong”)
"pong": これがブラウザに表示したい実際のデータ本体です。
 
(参考)代表的なHTTPステータスコード一覧
| 分類 | コード | 意味 | 説明 | 
|---|---|---|---|
| 1xx:Informational(情報) | 100 Continue | 続行 | リクエストを受け取り、処理を続けてよいことを示す。 | 
| 2xx:Success(成功) | 200 OK | 成功 | リクエストが正常に処理された。 | 
| 201 Created | 作成成功 | 新しいリソース(ファイルやデータ)が作成された。 | |
| 3xx:Redirection(転送) | 301 Moved Permanently | 恒久的転送 | 要求されたリソースが別のURLに恒久的に移動した。 | 
| 302 Found | 一時的転送 | 要求されたリソースが一時的に別のURLに移動している。 | |
| 304 Not Modified | 変更なし | 前回アクセス以降、内容に変更がない。キャッシュを利用してよい。 | |
| 4xx:Client Error(クライアント側のエラー) | 400 Bad Request | 不正なリクエスト | クライアントのリクエスト内容が不正。 | 
| 403 Forbidden | 禁止 | アクセス権がなく、要求が拒否された。 | |
| 404 Not Found | 見つからない | 指定したページやファイルがサーバに存在しない。 | |
| 5xx:Server Error(サーバ側のエラー) | 500 Internal Server Error | サーバ内部エラー | サーバ内部で予期しないエラーが発生した。 | 
| 503 Service Unavailable | 一時的に利用不可 | サーバが一時的に過負荷またはメンテナンス中。 | 
補足
5xx番台 → サーバ側のトラブル
例:CGIプログラムのバグ、サーバ過負荷など
4xx番台 → クライアント(ブラウザなど)のミス
例:URL間違い、権限なし、リクエストの形式不備など
4. /sensors で加速度をJSON返却
前回のステップ(3. HTTP サーバ)では「HTTPサーバを立てて /ping にアクセスすると pong と返す」ことを学びました。
つまり「決まった文字列(”pong”)を返す」だけのサーバです。
ここからはもう一歩進んで、センサーの値をリアルタイムに返すサーバを作ります。
M5StickC Plus2 には IMU(加速度センサー)が内蔵されており、その値を JSON 形式で返します。
JSONとは:データをやりとりするための世界的に使われているフォーマットです。
見た目は少しコードっぽいですが、人間にも読みやすいのが特徴です。
たとえば、
{"acc":[0.012, -0.980, 0.045]}
というデータは、
"acc"という名前のデータ(キー)があって、- その中に 
[0.012, -0.980, 0.045]という 数の並び(配列) が入っています。- → これはそれぞれ x軸・y軸・z軸の加速度 を意味します。
 
 

「文字列を返すサーバ」から「データを返すサーバ」に進化するのがこのステップです。
手順
/sensorsを追加して、IMU(加速度)を返すようにします。- 下のコードを丸ごと貼り替え → 書き込み。
※ ssidとpassの書き換え忘れずに! - ブラウザで 
http://<IP>/sensorsを開くと JSON が表示されます。 
M5側
コード(step04_http_sensors.ino)
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ
#include <WiFi.h>        // Wi-Fi 接続に必要な標準ライブラリ
#include <WebServer.h>   // HTTP サーバを簡単に立てるためのライブラリ
const char* ssid = "YOUR-SSID";   // ←自分のWi-FiのSSIDに変更
const char* pass = "YOUR-PASS";   // ←自分のWi-Fiのパスワードに変更
WebServer server(80);             // ポート80でHTTPサーバを作成(ブラウザから普通にアクセスできるポート)
// 加速度センサーの値をJSON形式で返す関数
String sensors(){
  float ax, ay, az; 
  M5.Imu.getAccel(&ax, &ay, &az);   // IMUから加速度データを取得(x, y, z軸)
  // 文字列を組み立てて {"acc":[x,y,z]} というJSONを作る
  String j = "{"; 
  j += "\"acc\":[" + String(ax,3) + "," + String(ay,3) + "," + String(az,3) + "]";
  j += "}"; 
  return j;                         // 完成したJSON文字列を返す
}
// ブラウザ(fetchなど)からのアクセスを許可するための設定(CORSヘッダを付ける)
void cors(){ 
  server.sendHeader("Access-Control-Allow-Origin","*");  // どこからのアクセスでも許可する
}
void setup(){
  auto cfg = M5.config();           // M5の設定を取得
  M5.begin(cfg);                    // M5StickC Plus2を初期化
  M5.Display.setRotation(1);        // 画面を横向きにする
  M5.Display.setTextSize(2);     // 文字サイズを大きめに
  M5.Display.fillScreen(TFT_BLACK); // 画面を黒でクリア
  // Wi-Fiに接続する処理
  WiFi.begin(ssid, pass); 
  while(WiFi.status() != WL_CONNECTED){ // 接続が完了するまで繰り返す
    delay(300);                         // 0.3秒待って再チェック
  } 
  // /ping にアクセスされたときの処理
  server.on("/ping", [](){ 
    cors();                                       // CORSヘッダを付ける
    server.send(200, "text/plain", "pong");       // ステータス200で "pong" を返す
  });
  // /sensors にアクセスされたときの処理
  server.on("/sensors", [](){ 
    cors();                                       // CORSヘッダを付ける
    server.send(200, "application/json", sensors()); // JSON形式のセンサー値を返す
  });
  // 存在しないURLにアクセスされたときの処理
  server.onNotFound([](){ 
    if(server.method() == HTTP_OPTIONS){          // もしOPTIONSメソッドなら(CORSの事前確認用)
      cors(); 
      server.send(204);                           // 204 No Content を返す
    } else {
      server.send(404,"text/plain","Not Found");  // それ以外は 404エラーを返す
    }
  });
  server.begin();   // HTTPサーバを開始する
  // M5の画面に自分のIPアドレスを表示
  M5.Display.setCursor(8,8); 
  M5.Display.printf("IP: %s\n", WiFi.localIP().toString().c_str());
  M5.Display.printf("step4");
}
void loop(){ 
  server.handleClient();   // クライアント(ブラウザなど)からのリクエストを処理する
}
確認方法
ブラウザで http://<M5のIP>/sensors を開く。私の場合は http://192.168.68.56/sensors 
次のように表示されればOKです!
(うまくいかない時はテザリングなどして他のネットワークで試してみてください)

解説
JSONを返す関数 sensors()
String sensors(){
  float ax, ay, az; 
  M5.Imu.getAccel(&ax, &ay, &az);
  String j = "{";
  j += "\"acc\":[" + String(ax,3) + "," + String(ay,3) + "," + String(az,3) + "]";
  j += "}";
  return j;
}
M5.Imu.getAccel(&ax, &ay, &az);で x, y, z の加速度を取得。String(ax,3)は 小数第3位までで文字列化(丸め)。
生の値は桁が長いので、読みやすさ&通信量の両面で3桁にしてます。
もっと精度が欲しいなら,4などに変更。- 組み立て結果は 
{"acc":[ax,ay,az]}。
JSONは「キー: 値」の組み合わせで書くのが基本。
ここではキーが"acc", 値が「3要素の配列」です。- きっちりJSONを作りたい人は 
ArduinoJsonなどの専用ライブラリもあります。 
 - きっちりJSONを作りたい人は 
 
server.on("/sensors", [](){
cors();
server.send(200, "application/json", sensors());
});
server.send(200, "application/json", sensors());200: ステータスコード「OK(成功)」です。“application/json”: ここでも Content-Type ヘッダが使われています。今度は「これから送るデータ(ボディ)は、JSON形式ですよ」とブラウザに教えています。これにより、ブラウザ(p5.js)は受け取ったデータをJSONとして正しく解釈できます。sensors(): 実際に返す中身(JSON文字列)で、メッセージボディにあたります。
これで「p5.jsから値を取得してグラフやアニメーションに反映する」ための下準備が整いました。
次はこのJSONを p5.js 側で読み込んで「円が傾きに応じて動く」可視化に進めます。
5. p5.js で可視化
前回までで、M5 は LAN に参加しており、さらに HTTP サーバとして
/ping→ “pong”/sensors→{"acc":[x,y,z]}(JSON)
を返せるようになっています。
今回は ブラウザ(p5.js)がクライアントになり、fetch() で http://<M5のIP>/sensors を定期的に取得 → 受け取った JSON をキャンバスにリアルタイム可視化します。
fetch()とは?
JavaScript の関数で、Web上のリソースを取りに行く(HTTPリクエストを送る)ための標準機能。
URL を指定すると、その結果を「Promise」という形で受け取れる。
例えば:
 fetch("http://M5のIP/sensors")
 → M5 が返す JSON({"acc":[...,...,...]})を取得できる。

手順
- 作業フォルダを作成:
p5-m5-sensors/ - その中に 
index.htmlとsketch.jsを作る。 - VSCodeでフォルダを開き、右下 Go Live(Live Server)を押す。
 - ブラウザで 
http://127.0.0.1:5500/が開く。 
M5側
先ほど(step04_http_sensors.ino)のまま
PC側(p5.js)
index.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>p5 x M5 HTTP</title>
    <!-- p5.js 本体(CDNから読み込み) -->
    <script src="https://cdn.jsdelivr.net/npm/p5@1.10.0/lib/p5.min.js"></script>
  </head>
  <body>
    <main></main>
    <!-- 自分のスケッチを最後に読み込む -->
    <script src="sketch.js"></script>
  </body>
</html>
sketch.js(※M5のIPに書き換える)
コード(sketch.js)
let acc = [0,0,0];                           // M5から受け取る加速度 [ax, ay, az] を保持する変数
const base = "http://192.168.1.xxx";         // ← ここを M5 の IP アドレスに置き換える(例: http://192.168.68.56)
function setup(){
  createCanvas(500, 300);                    // 幅500px, 高さ300px のキャンバスを作る(p5.jsの基本)
  // 100ms ごと(10Hz)に M5 の /sensors を取りに行く
  setInterval(async () => {
    try{
      const r = await fetch(base + "/sensors"); // HTTP GET で JSON を取得
      const j = await r.json();                  // レスポンスを JSON として解釈
      acc = j.acc || acc;                        // j.acc があれば更新、なければ前の値を維持
    }catch(e){
      // console.warn(e); // デバッグしたいときはコメントアウトを外す
    }
  }, 100);                                     // 100ms 間隔 = 10Hz
}
function draw(){
  background(245);                             // 背景を薄いグレーに
  noStroke();                                  // 円の枠線なし
  fill(30);                                    // 円の塗り色(濃いグレー)
  // acc[0], acc[1] を使って円の位置を決める(スケール係数 60)
  const cx = width/2  + acc[0] * 60;           // xは画面中央を基準に加速度で左右に移動
  const cy = height/2 + acc[1] * 60;           // yも画面中央を基準に上下に移動
  circle(cx, cy, 20);                           // 直径20pxの円を描く
  // 取得中の数値を文字で表示(小数2桁)
  fill(0);                                     // 文字色:黒
  textSize(14);                                // 文字サイズ:14px
  text(`acc: ${acc.map(v => v.toFixed(2)).join(', ')}`, 10, 20); // 左上に表示
}
確認:M5 を傾けると 円が動きます(うまくいかない時はテザリングなどして他のネットワークで試してみてください)

ブラウザ(p5.js)がクライアントとなり、M5(HTTPサーバ)から/sensorsのJSONを一定間隔で取得して表示(可視化)しました。ここで一方向の「読む」通信ができました。
次は逆方向、ブラウザからM5を操作します。HTTPのURLを叩く=コマンドとして使うイメージです。
これで「読むだけ」から「読んで・操作する」に発展します。
6. HTTP で M5 を制御(クリックで赤/黒)
M5StickC Plus2側に /fill/red と /fill/black を追加し、呼ばれた色で画面を塗りつぶす機能を用意します。p5.js 側では、クリックのたびに /fill/red と /fill/black を交互に fetch()して、M5 の画面色を切り替えます。

手順
- M5StickC Plus2 側に 
/fill/redと/fill/blackを追加。 - p5 側の 
mousePressed()で交互に叩く。 
M5側
コード:step06_http_fill.ino(ssidとpassを忘れずに書き換えること)
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ
#include <WiFi.h>        // Wi-Fi 接続のための標準ライブラリ
#include <WebServer.h>   // 簡易HTTPサーバを実装するためのライブラリ
const char* ssid = "YOUR-SSID";   // ←自分のWi-FiのSSIDに変更
const char* pass = "YOUR-PASS";   // ←自分のWi-Fiのパスワードに変更
WebServer server(80);             // ポート80でHTTPサーバを起動(ブラウザが普通にアクセスできる)
String sensors(){ float ax,ay,az; M5.Imu.getAccel(&ax,&ay,&az);               // IMU(加速度)値を取得
  String j="{"; j += "\"acc\":[" + String(ax,3) + "," + String(ay,3) + "," + String(az,3) + "]"; j += "}"; return j; } // {"acc":[x,y,z]} を文字列で返す
void cors(){ server.sendHeader("Access-Control-Allow-Origin","*"); }           // CORS許可(p5のfetchを通すため)
void setup(){
  auto cfg=M5.config(); M5.begin(cfg);                                         // M5本体の初期化
  M5.Display.setTextSize(2);     // 文字サイズを大きめに  
  M5.Display.setRotation(1); M5.Display.fillScreen(TFT_BLACK);                 // 画面を横向き&一度黒でクリア
  WiFi.begin(ssid, pass); while(WiFi.status()!=WL_CONNECTED){ delay(300);}     // Wi-Fi接続が完了するまで待機(300ms間隔で確認)
  server.on("/ping", [](){ cors(); server.send(200,"text/plain","pong"); });   // 動作確認用:/ping に GET で "pong" を返す
  server.on("/sensors", [](){ cors(); server.send(200,"application/json", sensors()); }); // /sensors に GET で 加速度のJSONを返す
  server.on("/fill/red", [](){ cors(); M5.Display.fillScreen(TFT_RED);   server.send(200,"text/plain","OK"); });   // /fill/red で画面を赤にして "OK"
  server.on("/fill/black", [](){ cors(); M5.Display.fillScreen(TFT_BLACK); server.send(200,"text/plain","OK"); }); // /fill/black で画面を黒にして "OK"
  server.onNotFound([](){ if(server.method()==HTTP_OPTIONS){ cors(); server.send(204);} else server.send(404,"text/plain","Not Found"); }); // 未定義URLやOPTIONSに対応
  server.begin();                                                               // HTTPサーバ開始
  M5.Display.setCursor(8,8);                                                    // 文字描画位置を左上に
  M5.Display.printf("IP: %s\n", WiFi.localIP().toString().c_str());            // 割り当てIPを画面に表示(ブラウザからアクセスするために見る)
  M5.Display.printf("step6");
}
void loop(){ server.handleClient(); }                                           // 受信したHTTPリクエストを処理し続ける
PC側(p5.js)
コード(index.html)※変更なし
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>p5 x M5 HTTP</title>
    <!-- p5.js 本体(CDNから読み込み) -->
    <script src="https://cdn.jsdelivr.net/npm/p5@1.10.0/lib/p5.min.js"></script>
  </head>
  <body>
    <main></main>
    <!-- 自分のスケッチを最後に読み込む -->
    <script src="sketch.js"></script>
  </body>
</html>
コード:sketch.js(mousePressed を追加、他は前のままです)
let acc = [0,0,0];                          // M5から受け取る加速度 [ax, ay, az] を格納する配列
const base = "http://192.168.1.xxx";        // ← M5のIPアドレスに置き換える(例: "http://192.168.68.56")
function setup(){
  //前のまま
}
function draw(){
  //前ののまま
}
//追加
let clickCount = 0;                     // クリック回数カウンタ
// マウスクリックやタップがあったときに呼ばれる
// クリックで赤↔黒をトグル
function mousePressed(){
  clickCount++;  // カウントアップ
  const colorPath = (clickCount % 2 === 1) ? "/fill/red" : "/fill/black";
  fetch(base + colorPath);  // M5にリクエスト送信
}
確認:クリックするたびに M5 の画面が 赤↔黒 に切り替わる。(ちょいちょい反応しないかも)

fetch() で /fill/red のようなURLにアクセスすることも、GETリクエストの応用です。 本来、GETメソッドは「データを取得する」ために使われますが (例: /sensors)、このようにURL自体を「サーバを操作するコマンド(命令)」として使うこともできます。 
HTTPでは、操作の種類を「メソッド」で表します。M5の例では GET を使って情報を取得していますが、ほかに POST, PUT, DELETE などがあります。
- GET:読み取る(例:
/sensors) - POST:送る(例:フォーム送信)
 - DELETE:削除要求
 
さて、HTTPで /sensors を読む、/fill/... で操作するところまで来ました。
次にやりたいのは、取ったデータを時系列で並べる・ログに残す・グラフの横軸にすること。
そのためには正しい時刻が必要です。
M5 の内蔵時計は電源を切るとズレるので、NTP(ネットワーク時刻)で毎回正しい時間を同期しておくのが定石です。
ここからは「時刻を合せる→画面に表示」までを行い、後続の可視化やログ保存の基準時刻にします。
7. NTP で時刻取得して画面表示
NTP(Network Time Protocol)を使って、インターネット上の時刻サーバから正しい現在時刻を取得します。日本時間(JST, UTC+9)に合わせ、毎秒M5の画面に日時を表示します。これにより、センサーデータや操作イベントに一貫したタイムスタンプを付けられるようになり、ログや可視化でズレが出にくいです。
使う関数のポイント
configTime(gmtOffsetSec, daylightOffsetSec, server1, server2)- ここで UTC+9時間 (= 9 * 3600秒) を指定(日本はサマータイムなしなので 
daylightOffsetSec = 0) 
- ここで UTC+9時間 (= 9 * 3600秒) を指定(日本はサマータイムなしなので 
 getLocalTime(&tmStruct)- NTP同期間に成功していれば、ローカルタイム(JST)で 
tm構造体が埋まります(失敗ならfalse) 
- NTP同期間に成功していれば、ローカルタイム(JST)で 
 
注意:NTP同期はWi-Fi接続が完了してから行います。初回同期は数秒かかることがあります。ssidとpassの書き換え忘れずに!
M5側
コード(step07_ntp_clock.ino)
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ
#include <WiFi.h>             // Wi-Fi接続に使う標準ライブラリ
const char* ssid = "YOUR-SSID";   // ←自分のWi-FiのSSIDに変更
const char* pass = "YOUR-PASS";   // ←自分のWi-Fiのパスワードに変更
void setup(){
  M5.begin();                                 // M5本体の初期化
  M5.Display.setRotation(1);                  // 画面を横向きに
  // 必要なら文字サイズなども設定可:M5.Display.setTextSize(2);
  // --- Wi-Fi接続(NTPはインターネット接続が前提) ---
  WiFi.begin(ssid, pass);                     // アクセスポイントへ接続開始
  while (WiFi.status() != WL_CONNECTED) {     // 接続が完了するまで待機
    M5.Display.print("...");
    delay(300);                               // 0.3秒間隔で確認
  }
  // --- NTP設定(JST: UTC+9、サマータイムなし) ---
  // 第1引数: 日本のオフセット(秒)= 9時間 * 3600秒
  // 第2引数: サマータイム補正(日本は 0)
  // 第3,4引数: 使うNTPサーバ(複数指定でフォールバック(倒れた時の代替手段))
  configTime(9 * 3600, 0, "ntp.nict.jp", "pool.ntp.org");
}
void loop(){
  struct tm t;                                // 時刻を受け取るための構造体(年/月/日/時/分/秒など)
  if (getLocalTime(&t)) {                     // NTP同期済みならローカル時刻(JST)が入る、失敗ならfalse
    M5.Display.fillScreen(TFT_BLACK);         // 画面を黒でクリア(毎秒全消しするので簡単・確実)
    M5.Display.setCursor(8, 8);               // 描画位置を左上へ
    // 年/月/日 時:分:秒 をゼロ埋めで整形して表示
    M5.Display.printf("%04d/%02d/%02d %02d:%02d:%02d\n",
      t.tm_year + 1900,                       // tm_year は 1900年からの経過年数 → 西暦に直す
      t.tm_mon + 1,                           // tm_mon は 0=1月 … 11=12月 → +1 する
      t.tm_mday,
      t.tm_hour,
      t.tm_min,
      t.tm_sec
    );
    M5.Display.printf("step7");
  } else {
    // まだNTPが同期できていない・一時的に失敗 → 待つだけ
    M5.Display.setCursor(8, 8);
    M5.Display.print("Syncing time...");
  }
  delay(1000);                                // 1秒おきに画面更新
}
M5の画面に時刻が表示されていればOKです。

8. /sensors に時刻を追加 → p5.jsで表示
これまでの /sensors(加速度) に、NTP(JST)由来の時刻を同封して返します。
2種類の時刻を入れます:
iso… 人間が読める ISO8601(例:2025-09-20T12:34:56+09:00)ts… 機械処理しやすい エポック(ミリ秒)(例:1695100000000)- 計測の際はよくこちらを使用
 
p5.js 側では 値+時刻を同時に表示し、最後に受け取ってからの経過時間(age) も出して取りこぼし・遅延の検知に使います。
- step04~06:HTTPで読む(/sensors)&操作する(/fill)
 - step07:NTPで正しい時刻を持つ
 - step08(本章):データ+時刻をひとまとめにして返す → ログ/グラフの基準時刻がそろう
 
M5側
/sensors に時刻を同封。
コード(step08_sensors_time.ino)(ssidとpassの変更忘れずに)
#include <M5StickCPlus2.h>  // M5StickC Plus2 専用のライブラリを読み込む
#include <M5Unified.h>  // M5Stackシリーズを統合的に扱えるライブラリ
#include <WiFi.h>                      // Wi-Fi接続に使う標準ライブラリ
#include <WebServer.h>                 // 簡易HTTPサーバのライブラリ
#include <time.h>                      // NTP/時刻関連(configTime, getLocalTime など)
const char* ssid = "YOUR-SSID";   // ←自分のWi-FiのSSIDに変更
const char* pass = "YOUR-PASS";   // ←自分のWi-Fiのパスワードに変更
WebServer server(80);                  // ポート80でHTTPサーバを起動
// CORS対応(ブラウザの fetch() を許可)
void cors(){
  server.sendHeader("Access-Control-Allow-Origin", "*");      // どこからのOriginでも許可
  server.sendHeader("Access-Control-Allow-Methods", "GET");   // GETを許可(必要に応じて追加)
  server.sendHeader("Access-Control-Allow-Headers", "*");     // ヘッダを許可(簡略)
}
// ISO8601形式(JST)文字列を作るヘルパー
String nowIsoJst(){
  struct tm t;                                                // 時刻を受け取る構造体
  if (getLocalTime(&t)) {                                     // NTP同期済みならtrue
    char buf[32];                                             // "YYYY-MM-DDTHH:MM:SS+09:00" 用のバッファ
    // 日本は常に +09:00(サマータイムなし)なので固定で付ける
    strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &t);      // まず日時までを整形
    return String(buf) + "+09:00";                            // タイムゾーン部分を付けて返す
  }
  return String("unsynced");                                  // まだ同期できていない場合のフォールバック
}
// エポック(秒)→ ミリ秒にして返す(同期前は0)
unsigned long long nowEpochMs(){
  time_t sec = time(nullptr);                                 // 現在時刻(秒)。同期前は 0 または1970年付近
  if (sec <= 1000) return 0ULL;                               // 明らかに未同期なら 0 を返す
  return (unsigned long long)sec * 1000ULL;                   // ミリ秒換算(粗いが十分)
}
// 加速度+時刻をJSONで返す関数
String sensorsJson(){
  float ax, ay, az;                                           // 加速度値(x, y, z)
  M5.Imu.getAccel(&ax, &ay, &az);                             // IMUから加速度を取得
  String iso = nowIsoJst();                                   // ISO8601の時刻文字列(JST)
  unsigned long long ts = nowEpochMs();                       // エポック(ms)
  
  // {"acc":[x,y,z], "ts": 1695100000000, "iso":"2025-09-20T12:34:56+09:00"} を組み立て
  String j = "{";                                             // JSON開始
  j += "\"acc\":[" + String(ax,3) + "," + String(ay,3) + "," + String(az,3) + "],";
  j += "\"ts\":" + String((unsigned long long)ts) + ",";      // 数値なのでダブルクォートなし
  j += "\"iso\":\"" + iso + "\"";                             // 文字列はダブルクォートで囲む
  j += "}";                                                   // JSON終了
  return j;                                                   // 完成したJSONを返す
}
void setup(){
  auto cfg = M5.config();                                     // M5の起動設定を取得
  M5.begin(cfg);                                              // M5本体を初期化
  M5.Display.setRotation(1);                                  // 画面を横向きに
  M5.Display.setTextSize(2);                                  // 文字を大きめに
  M5.Display.fillScreen(TFT_BLACK);                           // 背景を黒でクリア
  WiFi.begin(ssid, pass);                                     // Wi-Fi接続開始
  while (WiFi.status() != WL_CONNECTED) {                     // 接続完了まで待機
    M5.Display.setCursor(8, 8);                               // 画面左上
    M5.Display.printf("Connecting WiFi...\n");                // 接続中の表示(簡易)
    delay(300);                                               // 0.3秒待つ
  }
  // NTP設定(JST=UTC+9、サマータイムなし) 
  configTime(9*3600, 0, "ntp.nict.jp", "pool.ntp.org");       // 最低でも1つは同期サーバを入れておく
  // /ping → "pong"(生存確認)
  server.on("/ping", HTTP_GET, [](){
    cors();                                                   // CORSを許可
    server.send(200, "text/plain", "pong");                   // 文字列 "pong" を返す
  });
  // /sensors → 加速度+時刻のJSON
  server.on("/sensors", HTTP_GET, [](){
    cors();                                                   // CORSを許可
    server.send(200, "application/json", sensorsJson());      // JSONを返す
  });
  // /fill/red → 画面を赤に
  server.on("/fill/red", HTTP_GET, [](){
    cors();                                                   // CORSを許可
    M5.Display.fillScreen(TFT_RED);                           // 塗りつぶし
    server.send(200, "text/plain", "OK");                     // "OK" 応答
  });
  // /fill/black → 画面を黒に
  server.on("/fill/black", HTTP_GET, [](){
    cors();                                                   // CORSを許可
    M5.Display.fillScreen(TFT_BLACK);                         // 塗りつぶし
    server.send(200, "text/plain", "OK");                     // "OK" 応答
  });
  // 未定義URLやOPTIONSへの対応
  server.onNotFound([](){
    if (server.method() == HTTP_OPTIONS) {                    // 事前フライト(CORS)
      cors();                                                 // CORSを許可
      server.send(204);                                       // No Content
    } else {
      server.send(404, "text/plain", "Not Found");            // 見つからない
    }
  });
  server.begin();                                             // HTTPサーバ開始
  M5.Display.fillScreen(TFT_BLACK);                           // 画面を一度クリア
  M5.Display.setCursor(8, 8);                                 // 左上から
  M5.Display.printf("IP: %s\n", WiFi.localIP().toString().c_str()); // 割り当てIPを表示
  M5.Display.printf("step8: sensors + time\n");               // ラベル
}
void loop(){
  server.handleClient();                                      // HTTPリクエストを処理し続ける
}
PC側(p5.js)
時刻付きJSONを表示
コード(index.html)※これまでと全く同じ
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>p5 x M5 HTTP</title>
    <!-- p5.js 本体(CDNから読み込み) -->
    <script src="https://cdn.jsdelivr.net/npm/p5@1.10.0/lib/p5.min.js"></script>
  </head>
  <body>
    <main></main>
    <!-- 自分のスケッチを最後に読み込む -->
    <script src="sketch.js"></script>
  </body>
</html>
コード(sketch.js:step06からの差分のみ表示。step06のsketch.jsに適宜追加してみてください)
let acc = [0,0,0];                                // 加速度 [ax, ay, az]
let iso = "—";                                    // デバイス側のISO8601文字列(JST)
let ts  = 0;                                      // デバイス側のエポック(ms)
let lastOkAt = 0;                                 // 最後に正常に受信できた時刻(ブラウザのms)
const base = "http://192.168.1.xxx";              // ← M5のIPに置き換え(例: "http://192.168.68.56")
function setup(){
  createCanvas(600, 360);                         // キャンバス作成(少し広め)
  // 10Hzで /sensors を取りに行く
  setInterval(async () => {
    try{
      //前のまま
      iso = j.iso || iso;                         // ISO文字列(同期前は "unsynced" の可能性)
      ts  = typeof j.ts === "number" ? j.ts : ts; // 数値(ms)。j.ts が数値型かどうかをチェック。未同期なら 0 の可能性
      lastOkAt = performance.now();               // ブラウザ側での取得時刻を記録(ms)
    }catch(e){
      // 前のまま
    }
  }, 100);                                        // 100ms間隔
}
function draw(){
  //ここまで前のまま
 
 //以下を追加
  // 時刻(デバイス側のISO)をそのまま表示
  text(`iso (device JST): ${iso}`, 12, 44);
  // 遅延の目安(受信からの経過ms)— 見た目のヘルスチェック
  const ageMs = performance.now() - lastOkAt;     // 取得からの経過時間
  text(`age since last /sensors: ${ageMs.toFixed(0)} ms`, 12, 66);
  // デバイスのエポック(ms)。0 は「未同期」の合図
  text(`ts (device epoch ms): ${ts}`, 12, 88);
}
function mousePressed(){                         
//前のまま
}
確認:表示例

コード解説
M5側:
NTP/時刻関連
#include <time.h>
configTime(9*3600, 0, "ntp.nict.jp", "pool.ntp.org");
- JST(+9h)固定でNTP同期。サマータイム無し前提なので第二引数(DST)は0。
 - Wi-Fi確立後に呼ぶことで、以降 
getLocalTime()/time()が正しい値を返せる。 
nowIsoJst()
String nowIsoJst(){
  struct tm t;
  if (getLocalTime(&t)) {
    char buf[32];
    strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &t);
    return String(buf) + "+09:00";
  }
  return "unsynced";
}
- 人間が読みやすい表示用。未同期なら 
"unsynced"を返し、クライアント側で判定しやすく。 
nowEpochMs()
unsigned long long nowEpochMs(){
  time_t sec = time(nullptr);
  if (sec <= 1000) return 0ULL;     // 未同期の粗判定
  return (unsigned long long)sec * 1000ULL;
}
- データ同期待ち受け/時系列処理用。未同期は 
0を明示して扱いやすく。 
/sensors のJSON生成
String sensorsJson(){
  float ax, ay, az;
  M5.Imu.getAccel(&ax, &ay, &az);
  String iso = nowIsoJst();
  unsigned long long ts = nowEpochMs();
  String j="{";
  j += "\"acc\":[" + String(ax,3) + "," + String(ay,3) + "," + String(az,3) + "],";
  j += "\"ts\":" + String((unsigned long long)ts) + ",";
  j += "\"iso\":\"" + iso + "\"";
  j += "}";
  return j;
}
- 加速度+時刻(2表現)をひとかたまりで返す。
 - クライアントは 
tsが数値であれば採用、0やiso="unsynced"の場合は未同期扱い、といった分岐が可能。 
P5側:
| 変数名 | 型 | 役割 | 
|---|---|---|
iso | string | M5側が返す ISO8601 形式の時刻文字列(例: "2025-09-20T12:34:56+09:00")。NTP未同期時は "unsynced"。 | 
ts | number | M5側が返すエポック(UNIX時刻・ms単位)。未同期時は 0。数値かどうかをチェックして安全に扱う。 | 
lastOkAt | number | p5.js(ブラウザ側)で「最後に M5 から正常に /sensors を受信した時間(ms)」を performance.now() で記録。通信が途切れたときにどれくらい経ったか測れる。 | 
setInterval 内の処理(データ取得ループ)
iso = j.iso || iso;                         
ts  = typeof j.ts === "number" ? j.ts : ts; 
lastOkAt = performance.now();               
それぞれの意味:
iso = j.iso || iso;- M5の 
/sensorsが"iso"フィールドを返した場合、それを上書き。 - もし 
"iso"が存在しない(未同期など)ときは、前回の値を維持。 ||(論理和)で「未定義なら左を使う」という短絡評価テクニック。
- M5の 
 ts = typeof j.ts === "number" ? j.ts : ts;j.tsが数値(ms単位の時刻)なら採用。"unsynced"などの文字列なら無視して前の値を保持。- → 「NTP未同期」や「壊れたデータ」にも強い。
 
lastOkAt = performance.now();- fetch成功時のブラウザ側の現在時刻(高精度ms)を記録。
 - 通信が止まったときに「何ms前のデータなのか」を後で算出できる。
 
データの鮮度(遅延)を表示
const ageMs = performance.now() - lastOkAt;
text(`age since last /sensors: ${ageMs.toFixed(0)} ms`, 12, 66);
- 今(ブラウザの時刻)と、最後に受信したとき(
lastOkAt)の差を計算。 - 通信が順調なら 100〜200ms 程度、もし数千ms以上なら「通信途切れ or fetch失敗」。
- → リアルタイム通信の健康チェック指標。
 
 
デバイスのエポック時刻を表示
text(`ts (device epoch ms): ${ts}`, 12, 88);
- M5が返す 
tsを生の数値で出す(例:1695100000000)。 - これは「1970年からの経過ミリ秒」で、後で他のデータ(例:PC側ログ)と同期するために使える。
 - もし 
0なら「NTP同期してない」サイン。 
9. 課題
基礎課題
M5の加速度センサとp5.js(円などの描画)を連動させてみましょう。また、p5.jsからM5StickC Plus2を制御してみましょう。
提出内容(2つ)
- ソースコードをMoodleに提出してください。(自宅等で実施した場合、ssid/passは***/***などに変更してください)
- ~~~.ino (M5)
 - index.html(P5)
 - sketch.js(P5)
 
 - 動作中の動画をMoodleに提出してください。
- 条件(動画を見て以下がわかること)
- M5の動作がp5.jsの描画と連動していること
 - 時刻が取得できていること
 - p5.js側の操作でM5StickC Plus2が制御できていること
 
 
 - 条件(動画を見て以下がわかること)
 
発展課題
p5.jsでできるだけリアルな雪を降らせてみる + M5の加速度センサの値に応じて描画に変化をつける。
提出内容(2つ)
- ソースコードをMoodleに提出してください。
- ~~~.ino (M5)
 - index.html(P5)
 - sketch.js(P5)
 - reame.txt(工夫点あれば書く)
 
 - p5.jsとM5連動していることがわかる動画をMoodleに提出してください。
 
補足説明
HTTP/1.1/HTTP/2/HTTP/3 のちがい(WebSocketへ進む前に)
実際のWeb通信では、HTTP/1.1・HTTP/2・HTTP/3 の3世代が使われています。
どれも「ブラウザとサーバの間でデータをやり取りするルール(プロトコル)」ですが、通信の効率化の仕方が異なります。
| バージョン | 主な特徴 | 通信の流れ | 
|---|---|---|
| HTTP/1.1 (1997〜) | 1回の通信で1つのリクエストを順番に処理(順番待ち)。Keep-Aliveで同じ接続を再利用可能。 | 1リクエスト → 1レスポンス(直列) | 
| HTTP/2 (2015〜) | 1本のTCP接続の中で複数のリクエストをストリームとして同時並行で処理できる(多重化)。ヘッダ圧縮で高速化。 | 1接続で複数リクエストを並列送信 | 
| HTTP/3 (2022〜) | 通信の土台をTCP→QUIC(UDPベース)に変更。遅延やパケット損失の影響を受けにくく、モバイルや動画通信に強い。 | QUIC上で暗号化+並列通信(より安定) | 
HTTP/3 の特徴は「TCPではなくQUICという新しい通信プロトコルを使う」点です。
これにより、Wi-Fiを切り替えたときなどの接続維持や再送の効率化が向上しています。
ただし、やっていること自体は HTTP/2 と同様、リクエストとレスポンスの高速なやり取りです。
- HTTP/1.1:順番待ちで直列通信
 - HTTP/2:同時並行で効率化(ストリーム多重化)
 - HTTP/3:通信の足回りを刷新(QUICで低遅延・安定化)
 
注意点:
これらはどれも「HTTP」という要求–応答型の世界の話です。
一方、来週扱う WebSocket は「一度つないだら切るまで双方向に話せる」別プロトコル。
HTTP/1.1 の仕組みを使って最初に「Upgrade: websocket」という握手だけ行い、その後はHTTPを離れて常時通信モードに入る、という違いがあります。
WebSocket:最初に電話をかけて、切るまで「話し続ける」。
HTTP/1.1〜3:より速く・効率的に「郵便(要求と返事)」を届ける。
(参考)GETとPOSTメソッドの違い
HTTPリクエストには、GET 以外にも「何をしてほしいか」をサーバに伝えるためのメソッド(命令の種類)があります。その中でも特に重要なのが POST です。
GET と POST は、どちらも「クライアントからサーバへ情報を送る」ことができますが、その目的とデータの送り方が根本的に異なります。
1. GETメソッド
- 目的: 「データを取得する (GET)」
- サーバから情報をもらうのが主な役割です。
 - 例: Webページを表示する、センサーの値 (
/sensors) を問い合わせる。 
 - データの送り方: URLの一部(クエリ文字列)として送る。
http://example.com/search?q=testのように、?の後ろにキー=値の形でデータが付きます。
 - 特徴:
- 丸見え: データがURLに残るため、ブラウザの履歴やサーバのログに記録されます。ブックマークもできます。
 - 非機密: パスワードのような機密情報を送るのには適していません。
 - 長さ制限: URLには長さの制限があるため、送れるデータ量に限りがあります。
 - [補足] 今回の演習の 
/fill/redのように、URLを「コマンド」として使い、サーバの状態を変えることも可能ですが、基本は「取得」です。 
 
2. POSTメソッド
- 目的: 「データを送信・処理 (POST)」
- サーバにデータを渡し、何らかの処理(登録・更新など)をしてもらうのが主な役割です。
 - 例: ログインフォームの送信、掲示板への書き込み、ファイルのアップロード。
 
 - データの送り方: HTTPリクエストの「ボディ」にデータを入れて送る。
- URLは 
http://example.com/loginのようにシンプルです。 - データ(IDやパスワードなど)はURLからは見えない「本体(ボディ)」部分に梱包されて送られます。
 
 - URLは 
 - 特徴:
- 見えない: データがURLに出ません。機密情報(パスワードなど)を送るのに適しています。
 - 量が多いOK: URLのような長さ制限がないため、大きなデータ(画像やJSONなど)も送れます。
 - 更新時の警告: 
POSTでフォームを送信した後にブラウザで「更新」ボタンを押すと、「フォームを再送信しますか?」という警告が出ることがあります。これは、同じデータを二重に登録してしまうのを防ぐためです。