## 操作方法
1. バッテリーをつける
2. 2つのスイッチを下画像と逆方向にトグルする。(左側がマイコン、右側が7.4V用スイッチ)

3. Pythonプログラムを実行。
ページ下のプログラムをコピペ、保存して、以下コマンドで実行します。pipでinputs, bleakを入れて、`python3 main.py`でもよいです。
```
uv run --with inputs,bleak main.py
```
4. 操作方法

5. エラーなどがあり、最初からやり直す場合、マイコンのRボタンで再起動(スイッチのトグルでもよい)します。pythonプログラムも再実行します。
## 回路図


- マイコン(3.3か5V入力なので降圧する) 予備・準備で2個買う
[Seeed Studio XIAO ESP32C3](https://akizukidenshi.com/catalog/g/g117454/)
1F 3番売場
- 昇圧コンバーター DC/DC
~~[最大24V出力 昇圧型スイッチング電源モジュール LMR62421使用キット](https://akizukidenshi.com/catalog/g/g107608/)
1F 33番売場 3段目~~
~~[最大30V出力 昇圧型スイッチング電源モジュール NJW4131使用](https://akizukidenshi.com/catalog/g/g107406/)
2F 71番売場~~
[4.7V~12.6V出力 最大10A 同期式昇圧型スイッチング電源モジュール TPS61088使用](https://akizukidenshi.com/catalog/g/g114566/)
2F 71番売場
- バッテリー モーター、電磁弁用Lipo2セル(7.4V)
[DATA POWER TECHNOLOGY DTP502535-2S リチウムイオンポリマー電池 7.4V 400mAh](https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-5GHZ)
秋葉原本店取扱フロア:1階
- バッテリー(マイコン用) 3.7V
[DTP502535(PHR) 400mAhリチウムイオンポリマー電池 3.7V 400mAh](https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-4YZL)
秋葉原本店取扱フロア:1階
- モータードライバー(2系統ついてるやつ)
~~[モータードライバー TB6643KQ](https://akizukidenshi.com/catalog/g/g107688/)
1F 11番売場 上段スチール棚の出引~~
[DRV8835使用ステッピング&DCモータードライバーモジュール](https://akizukidenshi.com/catalog/g/g109848/)
1F 11番売場 スチール棚の上
- 電磁弁用のリレー
~~[リードリレーSS1A05D](https://akizukidenshi.com/catalog/g/g104328/)
1F 10番売場 D段~~
[5V小型リレー 接点容量:2A 2回路C接点 941H-2C-5D](https://akizukidenshi.com/catalog/g/g101229/)
1F 10番売場 D段
- ユニバーサル基盤(機体に乗せるのはこっち)
[片面ガラスコンポジット・ユニバーサル基板 Cタイプ めっき仕上げ (72×47mm) 日本製](https://akizukidenshi.com/catalog/g/g103229/)
1F 20番売場 上段スチール棚の引出
- LED(動作確認用)
[3mm赤色LED 625nm 70度 OSR5JA3Z74A](https://akizukidenshi.com/catalog/g/g111577/)
1F 50番売場 B列
[赤色7セグメントLED表示器 1文字カソードコモン ボディ黒 C-551SRD](https://akizukidenshi.com/catalog/g/g100640/)
これも買っておいていいかも
1F 50番売場 D列 右側スチール棚引出
- 抵抗
適当なもの
- リード線
適当なもの
- ブレッドボード
適当なもの
- はんだ
適当なもの
## 追加購入1
- [3V小型リレー 接点容量:2A 946H-1C-3D](https://akizukidenshi.com/catalog/g/g107651/)
1F 10番売場 D段
- LiPo充電器?
- [耐熱電子ワイヤー 1m×10色 導体外径0.75mm(AWG22相当)](https://akizukidenshi.com/catalog/g/g111641/)
基盤配線用のワイヤーとか?[参照](https://www.ekit-tech.com/?p=6644)
2F 68番売場
## 追加購入2
- [2.1mmジャックアダプター](https://duckduckgo.com/?q=2.1mm%E3%82%B8%E3%83%A3%E3%83%83%E3%82%AF&t=vivaldi&ia=web)
- リレー
- スイッチ
## 追加購入3
- [NchパワーMOSFET 30V260A IRLB3813PBF](https://akizukidenshi.com/catalog/g/g106270/)
1F 36番売場 2段目
- [汎用整流用ダイオード 1000V1A 1N4007](https://akizukidenshi.com/catalog/g/g100934/)
1F 34番売場 2段目
## マイコン
##コード**
```sketch=
#define SERVICE_UUID "12345678-1234-1234-1234-1234567890ab"
#define CHARACTERISTIC_UUID "abcdefab-1234-5678-1234-abcdefabcdef"
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <string> // C++のstring機能も念のためインクルード
// PIN DEFINITIONS
const int PIN_MD_AIN1 = 2;
const int PIN_MD_AIN2 = 3;
const int PIN_MD_BIN1 = 5;
const int PIN_MD_BIN2 = 4;
const int PIN_MD_DENZI = 20;
// PWM SETTINGS
#define PWM_FREQ 5000
#define PWM_RESOLUTION 8
const int jumptime[10] = {20,40,60,80,100,120,140,160,180,200};
// Global Pointers
BLEServer* pServer = nullptr;
BLECharacteristic* pCharacteristic = nullptr;
// Motor control helper function
void control_motor(int speed, int pin_fwd, int pin_rev) {
if (speed > 0) {
ledcWrite(pin_fwd, speed);
ledcWrite(pin_rev, 0);
} else if (speed < 0) {
ledcWrite(pin_fwd, 0);
ledcWrite(pin_rev, -speed);
} else {
ledcWrite(pin_fwd, 0);
ledcWrite(pin_rev, 0);
}
}
// Characteristic callbacks class
class MyCallbacks : public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic* pChar) override {
//
// 【最終修正】ここが唯一の変更点です。
// お使いの環境では、getValue()がstd::stringではなくArduinoのStringを返すため、型をこちらに合わせます。
//
String value = pChar->getValue();
if (value.length() > 0) {
Serial.print("受信: ");
Serial.println(value);
// ArduinoのString型でも .c_str() は使えるため、以下の解析ロジックは変更不要です
char cmd = value.charAt(0);
//int cnt=0;
if (cmd == 'M') { // Motor command "M,left,right"
char buffer[30];
value.toCharArray(buffer, sizeof(buffer)); // Stringをchar配列にコピー
char* token = strtok(buffer, ",");
if (token != NULL) {
token = strtok(NULL, ",");
if (token != NULL) {
int left_speed = atoi(token);
token = strtok(NULL, ",");
if (token != NULL) {
int right_speed = atoi(token);
Serial.printf("左モーター速度: %d, 右モーター速度: %d\n", left_speed, right_speed);
control_motor(left_speed, PIN_MD_AIN1, PIN_MD_AIN2);
control_motor(right_speed, PIN_MD_BIN1, PIN_MD_BIN2);
}
}
}
} else if (cmd == 'J') { // Jump command
char buffer[30];
value.toCharArray(buffer, sizeof(buffer)); // Stringをchar配列にコピー
char* token = strtok(buffer, ",");
if (token != NULL) {
token = strtok(NULL, ",");
if (token != NULL) {
int i = atoi(token);
digitalWrite(PIN_MD_DENZI, LOW);
delay(jumptime[i-1]);
digitalWrite(PIN_MD_DENZI, HIGH);
}
}
/**
Serial.println("ジャンプ動作を実行!");
digitalWrite(PIN_MD_DENZI, LOW);
delay(jumptime[cnt]);
digitalWrite(PIN_MD_DENZI, HIGH);
cnt++;
**/
} else {
Serial.println("不明なコマンドです。");
}
}
}
};
void setup() {
Serial.begin(115200);
// PWM setup
ledcAttach(PIN_MD_AIN1, PWM_FREQ, PWM_RESOLUTION);
ledcAttach(PIN_MD_AIN2, PWM_FREQ, PWM_RESOLUTION);
ledcAttach(PIN_MD_BIN1, PWM_FREQ, PWM_RESOLUTION);
ledcAttach(PIN_MD_BIN2, PWM_FREQ, PWM_RESOLUTION);
pinMode(PIN_MD_DENZI, OUTPUT);
digitalWrite(PIN_MD_DENZI, HIGH);
// BLE setup
BLEDevice::init("ESP32_BLE_MAKAIZO");
pServer = BLEDevice::createServer();
BLEService* pService = pServer->createService(SERVICE_UUID);
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_WRITE);
pCharacteristic->setCallbacks(new MyCallbacks());
pService->start();
BLEAdvertising* pAdvertising = BLEDevice::getAdvertising();
pAdvertising->addServiceUUID(SERVICE_UUID);
pAdvertising->setScanResponse(true);
BLEDevice::startAdvertising();
Serial.println("BLE デバイス初期化完了。アドバタイズ中...");
}
void loop() {
delay(2000);
}
```
## Python
**コード**
```Python=
import asyncio
from bleak import BleakClient, BleakScanner
import time
import math
from threading import Thread
class DualShock4Controller:
ANALOG_CENTER = 128
MAX_ANALOG_VAL = 127
DEADZONE = 20
LEVEL = 1
def __init__(self, loop):
self.LeftStickY = 0
self.RightStickY = 0
self.CirclePressed = False
self.ExitPressed = False
self.SlowModeActive = False
self.SuperSlowModeActive = False
self.is_split_mode = False
self.loop = loop
self._monitor_thread = Thread(target=self._monitor_controller, daemon=True)
self._monitor_thread.start()
def _notify_jumplevel(self):
t = self.LEVEL * 20
self.loop.call_soon_threadsafe(print, f"Current jump time is {t}ms")
def _notify_drive_mode(self):
mode_text = "分割モード (左右のスティックで独立操作)" if self.is_split_mode else "通常モード (左スティックで両輪操作)"
self.loop.call_soon_threadsafe(print, f"[Mode Change] {mode_text}")
def _monitor_controller(self):
while not self.ExitPressed:
try:
from inputs import get_gamepad
events = get_gamepad()
except Exception:
self.LeftStickY, self.RightStickY = 0, 0
time.sleep(1)
continue
for event in events:
if event.code == 'BTN_TL':
if event.state == 1:
self.SlowModeActive = True
elif event.state == 0:
self.SlowModeActive = False
elif event.code == 'BTN_TL2':
if event.state == 1:
self.SuperSlowModeActive = True
elif event.state == 0:
self.SuperSlowModeActive = False
elif event.code == 'ABS_Y':
self.LeftStickY = -(event.state - self.ANALOG_CENTER)
elif event.code == 'ABS_RY':
self.RightStickY = -(event.state - self.ANALOG_CENTER)
elif event.code == 'BTN_TR' and event.state == 1:
self.CirclePressed = True
elif event.code == "ABS_HAT0Y" and event.state == -1:
self.LEVEL = min(10, self.LEVEL + 1)
self._notify_jumplevel()
elif event.code == "ABS_HAT0Y" and event.state == 1:
self.LEVEL = max(1, self.LEVEL - 1)
self._notify_jumplevel()
elif event.code == "ABS_HAT0X" and (event.state == -1 or event.state == 1):
self.is_split_mode = not self.is_split_mode
self._notify_drive_mode()
def get_motor_speeds(self):
left = 0 if abs(self.LeftStickY) < self.DEADZONE else self.LeftStickY
right = 0 if abs(self.RightStickY) < self.DEADZONE else self.RightStickY
left_pwm = int((left / self.MAX_ANALOG_VAL) * 255)
right_pwm = int((right / self.MAX_ANALOG_VAL) * 255)
left_pwm = max(-255, min(255, left_pwm))
right_pwm = max(-255, min(255, right_pwm))
if not self.is_split_mode:
right_pwm = left_pwm
if self.SlowModeActive:
left_pwm = int(left_pwm * 3 / 4)
right_pwm = int(right_pwm * 3 / 4)
elif self.SuperSlowModeActive:
left_pwm = int(left_pwm / 2)
right_pwm = int(right_pwm / 2)
return left_pwm, right_pwm
def get_jumplevel(self):
return self.LEVEL
def jump(self):
if self.CirclePressed:
self.CirclePressed = False
return True
return False
def should_exit(self):
return self.ExitPressed
# --- BLE設定 ---
DEVICE_NAME = "ESP32_BLE_MAKAIZO"
SERVICE_UUID = "12345678-1234-1234-1234-1234567890ab"
CHARACTERISTIC_UUID = "abcdefab-1234-5678-1234-abcdefabcdef"
# --- メインロジック ---
async def find_device_address(name: str):
print("BLE デバイスをスキャン中…")
devices = await BleakScanner.discover(timeout=10)
for d in devices:
if d.name == name:
print(f"見つかったデバイス: {d.name} ({d.address})")
return d.address
print("指定したデバイスが見つかりませんでした。")
return None
async def run_client(address: str, controller: DualShock4Controller):
async with BleakClient(address) as client:
print("BLE 接続完了:", address)
print("左右スティックで操作,Rでジャンプ, L長押しで低速化.")
prev_command = ""
while not controller.should_exit():
left_motor, right_motor = controller.get_motor_speeds()
command = f"M,{left_motor},{right_motor}"
if command != prev_command:
await client.write_gatt_char(CHARACTERISTIC_UUID, command.encode())
print(f"[BLE] 送信: {command}")
prev_command = command
if controller.jump():
level = controller.get_jumplevel()
jump_command = f"J,{level}"
await client.write_gatt_char(CHARACTERISTIC_UUID, jump_command.encode())
print(f"[BLE] 送信: {jump_command}")
await asyncio.sleep(0.05)
print("\n[BLE] プログラムを終了します。")
async def main():
address = await find_device_address(DEVICE_NAME)
if address is None:
return
loop = asyncio.get_running_loop()
controller = DualShock4Controller(loop=loop)
await run_client(address, controller)
if __name__ == "__main__":
try:
asyncio.run(main())
except ImportError:
print("ライブラリ 'inputs' が見つかりません。 pip install inputs でインストールしてください。")
except Exception as e:
print(f"エラーが発生しました: {e}")
```