このコードの目的は:
✅ 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 起動 → 通常モードは省電力
コメント
この記事へのトラックバックはありません。







この記事へのコメントはありません。