以前の記事にて紹介した"pythonプログラムでps4コントローラーの入力を受け取る方法"の応用例として、PS4コントローラーのR2ボタンの押し込み量によって、サーボモーターの角度を変化させる方法について紹介します。
・サーボモーターの角度を細かく操作したい
この記事を読めばこんな感じのものが作れます
ps4コントローラーのR2ボタンの押し込み量に応じてサーボモーターの回転角が変化する仕組み 何かに使えないかな pic.twitter.com/bp1CTQQKEU
— 18倍界王拳 (@18bai_kaiohken) 2022年10月29日
今回やりたいことをざっくり説明
今回は、PS4コントローラーのR2ボタンの押し込み量に応じて、サーボモーターの角度が変化するような仕組みを作ります。
そのために、次のようなシステムを組むことにします。
1. pythonプログラムでPS4コントローラーの入力を受け取る
2. pythonプログラムで受け取ったコントローラーの入力を、wifi経由でESP32に送信
3. 受け取った入力に応じて、ESP32からサーボモーターへパルスを送信し、サーボを動かす
このシステムを作成するためには、①PS4コントローラーの入力をpythonで受け取る方法、②pythonプログラムとESP32の間でwifi通信を行う方法、という2つの前提知識が必要になります。
これら2つの方法については過去の記事にて紹介していますので、そちらを参考にしてください。
①PS4コントローラーの入力をpythonで受け取る方法に関する記事
potala123.hatenablog.com
②pythonプログラムとESP32の間でwifi通信を行う方法に関する記事
potala123.hatenablog.com
今回使用するプログラム
pythonプログラム
pythonプログラムでは、①ps4コントローラーの入力を認識する、②入力に応じた命令文を、ESP32にwifi経由で送信する
という2つの役割を担っています。
なお、今回はpygameというライブラリを用いてPS4コントローラーの入力を認識します。pygameライブラリの導入方法についてはこちらの記事にて紹介しています。
プログラムは以下の通りです。
import pygame import time import socket import math def sendMessage(message): message += "\n"#文末に改行コードを追加 client.sendall(bytes(message, encoding='ASCII'))#文字列をバイトに変換し送信(文字コードはASCIIを使用) print("サーバーへデータ送信") #pygame初期化部分 pygame.init() j = pygame.joystick.Joystick(0) j.init() print("pygame初期化完了") #wifi設定部分 ip_address = '192.168.1.17' #サーバー(ESP32のIPアドレス) port = 5000 #ポート番号 buffer_size = 4092 #一度に受け取るデータの大きさを指定 conection_status = False try: #クライアント用インスタンスを生成 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # サーバーに接続を要求する(IPアドレスとポート番号を指定) client.connect((ip_address, port)) while conection_status == False: #ESP32との接続が確立されるまで続ける data = client.recv(buffer_size) #サーバから送られてきたデータを読み取り(上限4092ビット) if data.decode() == "ready": #ESP32から接続完了の合図を受け取ったら conection_status = True #Trueに変更しループから出る print("サーバとの接続完了") print("コントローラーを操作してください") while True:#以下ループ events = pygame.event.get() for event in events: if event.type == pygame.JOYBUTTONDOWN: #ボタンが押された場合 if j.get_button(7): print("R2が押されました") print("R2の押し込み量") while j.get_axis(4) > -1: events = pygame.event.get() print(math.floor(j.get_axis(4)*100)) sendMessage("R2A:"+str(math.floor(j.get_axis(4)*100))) time.sleep(0.05) except KeyboardInterrupt: sendMessage("exit") client.close() print("ソケット通信を終了") print("プログラムを終了します") j.quit()
使用しているライブラリはpygameを除けばpython標準ライブラリなので、pygameを入れた環境であればコピペするだけで動きます。
pythonプログラムの簡単な解説
コントローラーの入力を受け取る部分や、ESP32と通信を行う部分に関する説明は先ほど紹介した過去記事を参考にしてください。
上のプログラムではまず初めに、j.get_axis(4)でR2の押し込み量(-1~1で表される。離した時-1,最大押し込みで1)を取得します。
ここで取得した値は小数点以下の桁数が非常に多く、扱いずらいです。そこで、取得した値に100をかけた後math.floor()とすることで小数点以下を四捨五入し、2~3桁の整数に変換しています。(例:取得した値が0.453222....の時、45を出力、取得した値が-1の時、-100を出力)
その後"sendMessage"メソッドで整数値をESP32に送信します。
以降はESP32側で、受け取った整数の値によってサーボモーターの角度を変更する処理を行っていきます。
ESP32用プログラム
ESP32側のプログラムは以下の通りです。(今回はArduino言語を使用しました)
//#ESP32-python(DUALSHOCK4使用) //ライブラリのインクルード #include <WiFi.h> #include <ESP32Servo.h> // Servoライブラリの読み込み const char* ssid = "aterm-23841f-g";//ルーターのSSID const char* password = "14b3384e2cc60";//ルーターのパスワード WiFiServer server(5000);//ポート番号5000でサーバーとして使用する String message; String oneLetter; Servo myservo; // Servoオブジェクトの宣言 const int SV_PIN = 13; // サーボモーターをデジタルピン13に int servo_angle = 0;//サーボ角度用変数(電源ON時にゼロ度に初期化) void setup() { Serial.begin(115200);//ビットレート115200でシリアル通信を開始 Serial.println(); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.begin(ssid, password);//アクセスポイントに接続 while (WiFi.status() != WL_CONNECTED) {//接続が完了しない場合"・"を打って待機 delay(500); Serial.print("."); } //接続が完了したら以下の内容を表示 Serial.println(""); Serial.println("WiFi connected."); Serial.println("IP address: "); Serial.println(WiFi.localIP());//自身のIPアドレスを表示 server.begin();//サーバー開始 myservo.attach(SV_PIN, 500, 2400); // サーボの割当(パルス幅500~2400msに指定) myservo.write(servo_angle);//サーボ角度を0度にリセット delay(100); } int value = 0; void loop(){ WiFiClient client = server.available();//サーバーに接続され、読み取り可能なデータがあるクライアントを取得 if (client) {//クライアントの情報が取得出来た場合(クライアントに対しサーバーが開かれている場合) Serial.println("New Client."); if (client.connected()){ message = "ready"; client.print(message);//クライアントへメッセージを送信 } String currentLine = "";//クライアントから得たデータを格納するための文字列を用意 String orderCode = "";//命令文の種類を格納 int orderAmount[2];//命令文の数値を格納するための要素数2の配列 int ref = 0;//配列の要素数カウント用 while (client.connected()) { // client.connected()は接続時True,切断時Falth if (client.available()) { // client.abalableは読み込み可能なバイト数(接続先のサーバーによってクライアントに書き込まれたデータの量)を返す char c = client.read(); //クライアントから送信されたデータを1バイトだけ読み取る if (c == '\n') { // 読み取った文字が改行コード(/n)だった場合 if (currentLine == "exit"){ client.stop();//サーバーとの接続を切断 Serial.println("Client Disconnected."); }else{ orderAmount[ref] = currentLine.toInt();; currentLine = "";//文字列の初期化 if (orderCode == "R2A"){ Serial.println("R2押し込み量"); Serial.println(orderAmount[0]); Serial.println("サーボ角度"); //Serial.print(round(0.9*(orderAmount[0]+100))); //Serial.print("度"); myservo.write(round(0.9*(orderAmount[0]+100))); } ref = 0;//初期化 orderCode = "";//初期化 currentLine = "";//文字列の初期化 } } else if (c == ':') { orderCode = currentLine; currentLine = "";//文字列の初期化 } else if (c == ',') { orderAmount[ref] = currentLine.toInt(); ref += 1; currentLine = "";//文字列の初期化 } else if (c != '\r') { // 受け取った文字が改行コードではない場合(普通の文字の場合) currentLine += c; //変数currentLineに受け取った文字を追加する } } } } }
ESP32プログラムの簡単な解説
ESP32側のプログラムでは、先ほど紹介したpythonプログラムから受け取った整数に対して次のような処理を行うことで、サーボモータの回転角を決定しています。
2.今回はサーボモーターの角度が、R2を押していないとき0°、最大押し込み時に180°となるようにしたいので、180/200×受け取った整数、すなわち、”0.9×受け取った整数”とすることで、R2ボタンの押し込み量をサーボモーターの角度に変換する
改善できそうな点
R2ボタンの押し込み量をわずかに変化させた場合にサーボモーターの動きがカクついてしまったり、そもそも全体的に動きがもっさりしているような感じがしました。
サーボへ指示を出す頻度を変えたり、サーボ回転角を決定する仕組みを工夫すれば、もっさり感やカクつきが低減できるかもしれないです。