このコードの目的は:
✅ ESP32-S3(XIAO ESP32S3)を USB HID マウスとして動作させる
✅ GPIOスイッチ入力でマウスのクリックや移動を制御する
✅ Wi-Fiアクセスポイントを起動して、スマホやPCのブラウザからマウス座標やディレイ時間を設定・保存できるようにする
✅ 保存した設定値は電源を切っても保持される
✅ 通常モードでは保存した設定値を使って自動で動作する
🎯 ✅ 全体構成の概要
このコードは以下の機能で構成されています:
1️⃣ 設定モードと通常モードの切り替え
→ 電源ON時に特定のスイッチを長押しすると設定モードに入る
→ 設定モードでは Wi-Fi AP を起動し、Webブラウザから設定画面を提供
→ 通常モードでは保存済みの設定値で動作
2️⃣ Webブラウザ設定画面
→ マウス移動量(座標値)と初期ディレイ時間を入力できる
→ 入力値はブラウザで範囲チェックされる(±127や1000~60000msなど)
→ サーバ側でも入力値の範囲制限を行い安全性を確保
→ 設定値を保存すると Preferences に記録(電源OFFでも保持)
3️⃣ 保存された設定値に基づく動作
→ 通常モードでは loop() に入ったときに一度だけ保存されたディレイ時間だけ待ち、その後保存された座標にマウスを相対移動
→ GPIOスイッチを押すとクリックや追加の移動操作ができる
4️⃣ 設定モード時の補助動作
→ 設定モード中はLEDが点灯
→ カーソルを右上隅に移動して操作しづらい位置に退避
🎯 ✅ このコードで実現すること
このコードを使うと:
✅ 一度スマホやPCから Wi-Fi 経由で移動量やディレイ時間を設定
✅ 設定内容がデバイス内部に保存される
✅ 電源を入れるたび自動で設定通り動作(自動操舵ONOFFその他スイッチとして)
✅ 操作はGPIOスイッチで簡単に制御
✅ 設定値を変更したいときは再び設定モードに入るだけ
という 「設定が簡単で一度設定すれば自律的に動作するカスタムUSBデバイス」 を実現できます。
#include <WiFi.h> #include <WebServer.h> #include <Preferences.h> #include "Adafruit_TinyUSB.h" // GPIO定義 const int tactA = 0; const int tactB = 1; const int setupSwitch = 2; const int LED_PIN = 3; // Wi-Fi設定 const char* ssid = "ESP32S3-AP"; // Webサーバ WebServer server(80); // USB HIDマウス Adafruit_USBD_HID usb_hid; uint8_t mouse_report[4] = {0}; // 座標変数(初期値0) int CURSOR_X = 0; int CURSOR_Y = 0; int MOVE_FILL_X = 0; int MOVE_FILL_Y = 0; int MOVE_RETURN_X = 0; int MOVE_RETURN_Y = 0; unsigned long initialMoveDelayMs = 10000; // デフォルト10秒 Preferences prefs; bool setupMode = false; bool movedInitial = false; // HTMLページ String htmlPage = R"rawliteral( <!DOCTYPE html> <html> <head> <title>自動操舵ONOFF設定</title> <style> body { font-family: Arial, sans-serif; background: #f0f0f0; padding: 20px; } fieldset { background: #fff; border: 1px solid #ccc; padding: 15px; margin-bottom: 20px; } legend { font-weight: bold; } label { display: inline-block; width: 140px; } input[type=number] { width: 80px; } .note { font-size: 0.9em; color: #555; } .button { padding: 10px 20px; margin-top: 10px; } </style> </head> <body> <h2>自動操舵 カーソル位置設定</h2> <form action="/set" method="POST"> <fieldset> <legend>自動操舵ONOFF</legend> <label><b>CURSOR_X</b>:</label> <input type="number" name="CURSOR_X" value="%CURSOR_X%" min="-127" max="127" required> <small class="note">現在値: %CURSOR_X%</small><br> <label><b>CURSOR_Y</b>:</label> <input type="number" name="CURSOR_Y" value="%CURSOR_Y%" min="-127" max="127" required> <small class="note">現在値: %CURSOR_Y%</small><br> </fieldset> <fieldset> <legend>カスタムONOFF</legend> <label><b>MOVE_FILL_X</b>:</label> <input type="number" name="MOVE_FILL_X" value="%MOVE_FILL_X%" min="-127" max="127" required> <small class="note">現在値: %MOVE_FILL_X%</small><br> <label><b>MOVE_FILL_Y</b>:</label> <input type="number" name="MOVE_FILL_Y" value="%MOVE_FILL_Y%" min="-127" max="127" required> <small class="note">現在値: %MOVE_FILL_Y%</small><br> <label><b>MOVE_RETURN_X</b>:</label> <input type="number" name="MOVE_RETURN_X" value="%MOVE_RETURN_X%" min="-127" max="127" required> <small class="note">現在値: %MOVE_RETURN_X%</small><br> <label><b>MOVE_RETURN_Y</b>:</label> <input type="number" name="MOVE_RETURN_Y" value="%MOVE_RETURN_Y%" min="-127" max="127" required> <small class="note">現在値: %MOVE_RETURN_Y%</small><br> </fieldset> <fieldset> <legend>初期ディレイ</legend> <label><b>initialMoveDelaySec</b>:</label> <input type="number" name="initialMoveDelaySec" value="%initialMoveDelaySec%" min="1" max="60" required> <small class="note">現在値: %initialMoveDelaySec% (秒)</small><br> </fieldset> <input type="submit" value="保存" class="button"> <p class="note">※座標: -127~+127、ディレイ: 1~60秒</p> </form> <form action="/testclick" method="GET"> <input type="submit" value="自動操舵テスト" class="button"> <small class="note">→ CURSOR_X, CURSOR_Y に移動後クリック</small> </form> <form action="/testmove" method="GET"> <input type="submit" value="カスタムONOFFテスト" class="button"> <small class="note">→ MOVE_FILL_X, MOVE_FILL_Y に移動後クリック → MOVE_RETURN_X, MOVE_RETURN_Y に戻る</small> </form> </body> </html> )rawliteral"; int constrainMouseValue(int value) { if (value < -127) return -127; if (value > 127) return 127; return value; } unsigned long constrainDelaySeconds(unsigned long value) { if (value < 1) return 1; if (value > 60) return 60; return value; } String processor(const String& var) { if (var == "CURSOR_X") return String(CURSOR_X); if (var == "CURSOR_Y") return String(CURSOR_Y); if (var == "MOVE_FILL_X") return String(MOVE_FILL_X); if (var == "MOVE_FILL_Y") return String(MOVE_FILL_Y); if (var == "MOVE_RETURN_X") return String(MOVE_RETURN_X); if (var == "MOVE_RETURN_Y") return String(MOVE_RETURN_Y); if (var == "initialMoveDelaySec") return String(initialMoveDelayMs / 1000); return ""; } void handleRoot() { String page = htmlPage; page.replace("%CURSOR_X%", String(CURSOR_X)); page.replace("%CURSOR_Y%", String(CURSOR_Y)); page.replace("%MOVE_FILL_X%", String(MOVE_FILL_X)); page.replace("%MOVE_FILL_Y%", String(MOVE_FILL_Y)); page.replace("%MOVE_RETURN_X%", String(MOVE_RETURN_X)); page.replace("%MOVE_RETURN_Y%", String(MOVE_RETURN_Y)); page.replace("%initialMoveDelaySec%", String(initialMoveDelayMs / 1000)); server.send(200, "text/html", page); } void handleSet() { if (server.hasArg("CURSOR_X")) CURSOR_X = constrainMouseValue(server.arg("CURSOR_X").toInt()); if (server.hasArg("CURSOR_Y")) CURSOR_Y = constrainMouseValue(server.arg("CURSOR_Y").toInt()); if (server.hasArg("MOVE_FILL_X")) MOVE_FILL_X = constrainMouseValue(server.arg("MOVE_FILL_X").toInt()); if (server.hasArg("MOVE_FILL_Y")) MOVE_FILL_Y = constrainMouseValue(server.arg("MOVE_FILL_Y").toInt()); if (server.hasArg("MOVE_RETURN_X")) MOVE_RETURN_X = constrainMouseValue(server.arg("MOVE_RETURN_X").toInt()); if (server.hasArg("MOVE_RETURN_Y")) MOVE_RETURN_Y = constrainMouseValue(server.arg("MOVE_RETURN_Y").toInt()); if (server.hasArg("initialMoveDelaySec")) initialMoveDelayMs = constrainDelaySeconds(server.arg("initialMoveDelaySec").toInt()) * 1000; prefs.putInt("CURSOR_X", CURSOR_X); prefs.putInt("CURSOR_Y", CURSOR_Y); prefs.putInt("MOVE_FILL_X", MOVE_FILL_X); prefs.putInt("MOVE_FILL_Y", MOVE_FILL_Y); prefs.putInt("MOVE_RETURN_X", MOVE_RETURN_X); prefs.putInt("MOVE_RETURN_Y", MOVE_RETURN_Y); prefs.putULong("initialMoveDelayMs", initialMoveDelayMs); server.sendHeader("Location", "/"); server.send(303); } void handleTestClick() { mouse_report[0] = 0; mouse_report[1] = CURSOR_X; mouse_report[2] = CURSOR_Y; usb_hid.sendReport(1, mouse_report, 4); delay(100); mouse_report[0] = 1; usb_hid.sendReport(1, mouse_report, 4); delay(100); mouse_report[0] = 0; usb_hid.sendReport(1, mouse_report, 4); server.sendHeader("Location", "/"); server.send(303); } void handleTestMove() { mouse_report[0] = 0; mouse_report[1] = MOVE_FILL_X; mouse_report[2] = MOVE_FILL_Y; usb_hid.sendReport(1, mouse_report, 4); delay(100); mouse_report[0] = 1; usb_hid.sendReport(1, mouse_report, 4); delay(100); mouse_report[0] = 0; mouse_report[1] = MOVE_RETURN_X; mouse_report[2] = MOVE_RETURN_Y; usb_hid.sendReport(1, mouse_report, 4); server.sendHeader("Location", "/"); server.send(303); } void setup() { pinMode(tactA, INPUT_PULLDOWN); pinMode(tactB, INPUT_PULLDOWN); pinMode(setupSwitch, INPUT_PULLUP); pinMode(LED_PIN, OUTPUT); digitalWrite(LED_PIN, LOW); usb_hid.begin(); prefs.begin("config", false); CURSOR_X = prefs.getInt("CURSOR_X", CURSOR_X); CURSOR_Y = prefs.getInt("CURSOR_Y", CURSOR_Y); MOVE_FILL_X = prefs.getInt("MOVE_FILL_X", MOVE_FILL_X); MOVE_FILL_Y = prefs.getInt("MOVE_FILL_Y", MOVE_FILL_Y); MOVE_RETURN_X = prefs.getInt("MOVE_RETURN_X", MOVE_RETURN_X); MOVE_RETURN_Y = prefs.getInt("MOVE_RETURN_Y", MOVE_RETURN_Y); initialMoveDelayMs = prefs.getULong("initialMoveDelayMs", initialMoveDelayMs); unsigned long pressTime = 0; const unsigned long threshold = 2000; if (digitalRead(setupSwitch) == LOW) { pressTime = millis(); while (digitalRead(setupSwitch) == LOW) { if (millis() - pressTime >= threshold) { setupMode = true; break; } } } if (setupMode) { digitalWrite(LED_PIN, HIGH); WiFi.softAP(ssid); server.on("/", handleRoot); server.on("/set", HTTP_POST, handleSet); server.on("/testclick", HTTP_GET, handleTestClick); server.on("/testmove", HTTP_GET, handleTestMove); server.begin(); for (int i = 0; i < 10; i++) { mouse_report[0] = 0; mouse_report[1] = -127; mouse_report[2] = -127; usb_hid.sendReport(1, mouse_report, 4); delay(50); } for (int i = 0; i < 20; i++) { mouse_report[0] = 0; mouse_report[1] = 127; mouse_report[2] = 0; usb_hid.sendReport(1, mouse_report, 4); delay(50); } } else { digitalWrite(LED_PIN, LOW); } while (!TinyUSBDevice.mounted()) delay(10); } void loop() { if (!setupMode && !movedInitial) { delay(initialMoveDelayMs); mouse_report[0] = 0; mouse_report[1] = CURSOR_X; mouse_report[2] = CURSOR_Y; usb_hid.sendReport(1, mouse_report, 4); movedInitial = true; } if (setupMode) { server.handleClient(); } bool nowA = digitalRead(tactA); bool nowB = digitalRead(tactB); if (nowA) { mouse_report[0] = 1; usb_hid.sendReport(1, mouse_report, 4); delay(100); mouse_report[0] = 0; usb_hid.sendReport(1, mouse_report, 4); } if (nowB) { mouse_report[0] = 0; mouse_report[1] = MOVE_FILL_X; mouse_report[2] = MOVE_FILL_Y; usb_hid.sendReport(1, mouse_report, 4); delay(100); mouse_report[0] = 1; usb_hid.sendReport(1, mouse_report, 4); delay(100); mouse_report[0] = 0; mouse_report[1] = MOVE_RETURN_X; mouse_report[2] = MOVE_RETURN_Y; usb_hid.sendReport(1, mouse_report, 4); } delay(10); }
🎯 ✅ 2️⃣ 各パートの説明 🚩 ① 変数・定義
const int tactA = 0; const int tactB = 1; onst int setupSwitch = 2; const int LED_PIN = 3;
GPIOピンの番号
tactA / tactB: 操作用スイッチ
setupSwitch: 設定モードに入るためのスイッチ
LED_PIN: 設定モード中はLED点灯
📝 座標関連変数 & ディレイ変数も初期値0/10000で定義
int CURSOR_X = 0; int CURSOR_Y = 0; unsigned long initialMoveDelayMs = 10000; // 10秒
Preferences用の保存領域 prefs、Webサーバ server、USB HID usb_hid も初期化。
🚩 ② setup() の流れ
✅ GPIOピンモード初期化
✅ USB HID 開始
✅ Preferencesから保存済み値を読み込み(座標、delay値)
CURSOR_X = prefs.getInt("CURSOR_X", CURSOR_X); initialMoveDelayMs = prefs.getULong("initialMoveDelayMs", initialMoveDelayMs);
✅ setupSwitch を2秒間長押し → 設定モードに入る
→ 設定モードでは:
Wi-Fi AP モード開始
Webサーバ起動(ブラウザ設定用ページ)
LED点灯
マウスカーソルを右上隅に相対移動する処理
🚩 ③ 設定モードのWeb UI
ブラウザからアクセスすると:
✅ 現在の CURSOR_X, CURSOR_Y や initialMoveDelayMs が入力欄に反映
✅ フォームから値を送信 → handleSet() で受信
✅ サーバ側でも 座標値は -127~127 に制限、delay値は 1000~60000 に制限
✅ 保存した値は Preferences に記録 → 電源OFFでも保持
prefs.putInt("CURSOR_X", CURSOR_X); prefs.putULong("initialMoveDelayMs", initialMoveDelayMs);
✅ Web画面には 「現在値」表示 や 入力範囲説明 も記載
🚩 ④ loop() の通常モード処理
✅ 通常モード(設定モードじゃない)では loop() に入ったとき一度だけ:
delay(initialMoveDelayMs); mouse_report[1] = CURSOR_X; mouse_report[2] = CURSOR_Y; usb_hid.sendReport(1, mouse_report, 4); movedInitial = true;
→ 保存 delay 時間だけ待つ → 保存された CURSOR_X, CURSOR_Y に相対移動
→ movedInitial=true にして再度移動しないように制御。
🚩 ⑤ 操作用GPIOの処理(常時有効)
✅ tactA を押す → マウス左クリック
✅ tactB を押す → MOVE_FILL_X, MOVE_FILL_Y に移動 → クリック → MOVE_RETURN_X, MOVE_RETURN_Y に戻る
→ これらは loop 内で GPIO入力が HIGH になったら即座に実行
🚩 ⑥ 設定モード時は Web サーバ動作
設定モード時:
✅ server.handleClient() で ブラウザからのリクエストに応答
→ / → 設定画面
→ /set → 設定保存
→ /testclick → CURSOR_X, CURSOR_Y に移動&クリック
→ /testmove → MOVE_FILL_X, MOVE_FILL_Y に移動→クリック→戻り
🎯 ✅ 全体の動作イメージ
1️⃣ 電源ON
→ setupSwitch 長押し → 設定モード(Wi-Fi AP + Web UI)
→ setupSwitch 長押しなし → 通常モード
2️⃣ 通常モードの流れ:
→ loop() に入ったタイミングで 保存済み delay 時間待機
→ 1度だけ CURSOR_X, CURSOR_Y に相対移動
→ 以降は GPIO操作や外部コマンドでマウス動作
🎯 ✅ このコードの特徴まとめ
✅ ブラウザ上で GUI から操作・設定保存可能
✅ 保存値は電源OFFでも維持
✅ loop開始時の自動移動タイミング delay も設定可能
✅ 設定モード時のみ Wi-Fi 起動 → 通常モードは省電力
コメント
この記事へのトラックバックはありません。
この記事へのコメントはありません。