SwitchのPro Controllerに連射機能をつけてみた
はじめに
最近スプラトゥーン2をやってる。
スプラトゥーンにおいて連射機能は必須ではないけど、特定の場面で有利に働くため、プロコンには搭載していない「連射」をしたいと思っている。
それについて最近調べたこと、できたことを書く。
連射機能を実現するには
純正品であるプロコンには搭載されてはいないけど、サードパーティ製の製品には連射機能つきが販売している。
お金を払えば連射はできる。でもサードパーティ製には、普段使っているプロコンとボタンの押し心地と異なったり、独自仕様があって、使っていて違和感がある。僕は慣れるまでのストレスを受け入れたくないと思ったので、プロコンっぽいテイストの中華製コントローラーを買ってみた。非常にプロコンっぽくて最初は満足していたんだけど、品質に問題があって使うのをやめた。
youtubeにプロコンに連射機能をつけた、という動画を見つけてこれじゃん、と思って動画と同じ構成で作ってみた。
https://www.youtube.com/watch?v=N1dPFDSxaQ4
とはいえ、Raspberry Pi を使ったところでデメリットは見えていて、漠然と運用コストが発生するだろうなとは思っていた。(具体的には後述する)
やりかた
Raspberry Piのセットアップ方法は https://mtosak-tech.hatenablog.jp/entry/2020/08/22/114622 に書いている。 簡単にRaspberry Piを使った「連射」の仕組みを説明すると、swtichとプロコンの間に Raspberry Pi をUSBで接続し、プロコンの入力をRaspberry Piで仲介してswtichに出力する。仲介するときに特定のキー入力を検出して、書き換えると連射はできた。
仲介するコードは、 https://mzyy94.com/blog/2020/03/20/nintendo-switch-pro-controller-usb-gadget/ にある。
本家はpythonで書かれていたけど、僕はrubyに慣れているのでrubyで書き直したけど、USBコントローラーとして認識されにくく、かつパフォーマンスが悪くてプロコンからの入力が遅延した。
ボトルネックは調べていないけど、 IO::EAGAINWaitReadable
がかなりの頻度で発生するのでここが遅いのかなと思ったが、関係ない気がしてきた。わからん。
しぶしぶpythonで動かすことにした。
STDOUT.sync = false
Thread.abort_on_exception = true
class RappidFireProCon
class ProConRejected < StandardError; end
PROCON_PATH = "/dev/hidraw0"
def initialize
connect
unless File.exist?(PROCON_PATH)
puts "プロコンをラズベイに挿してください"
loop do
break if File.exist?(PROCON_PATH)
sleep(1)
end
end
@gadget = File.open('/dev/hidg0', "w+")
@procon = File.open(PROCON_PATH, "w+")
end
def run_loop
Thread.new do
loop do
input = @gadget.read_nonblock(128)
inspect(input) { |value| puts ">>> #{value}" }
@procon.write_nonblock(input)
rescue IO::EAGAINWaitReadable
# puts ">>> no-op"
rescue Errno::EIO, Errno::ENODEV, Errno::EPROTO, IOError => e
raise ProConRejected.new(e)
end
end
Thread.new do
loop do
output = @procon.read_nonblock(128)
inspect(output) { |value| puts "<<< #{value}" }
@gadget.write_nonblock(output)
rescue IO::EAGAINWaitReadable
# puts "<<< no-op"
rescue Errno::EIO, Errno::ENODEV, Errno::EPROTO, IOError => e
raise ProConRejected.new(e)
end
end
loop { sleep(5) }
ensure
@gadget&.close
@procon&.close
end
private
def inspect(value)
yield value.bytes.map{|x| x.to_s(16).rjust(2, "0") }.join
end
def connect
system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
sleep(2)
end
end
loop do
runner = RappidFireProCon.new
runner.run_loop
rescue RappidFireProCon::ProConRejected => e
puts e
puts "------------"
puts 'プロコンが外されました'
retry
end
Raspberry Piで連射機能を実現した結果
体感でわかる遅延はないように思った。十分使える。
が、前述した運用コストや自動化が難しい手動なオペレーションがいくつか存在することがわかった。
具体的には、プログラムを停止するとプロコンがbluetoothでswitchと接続してしまうので、Raspberry Piと再度USB接続するには、bluetoothでの接続を切断する必要がある。これを自動化したい、いまは、別のswtichに繋げて切断しているが、もしかしたら、bluetooth経由でRaspberry Piに接続できると接続の問題が解決するかもしれない。
プログラムの例外処理がまだ甘いので、起動時にはsshしてログを見ながら状態の確認をしつつ、自動化できそうな部分は都度手を入れている。
プロコンの入力は、秒間20回らしいので、これに合わせてsleepを入れるなど、リソースを節約をする処理を入れるともっと安定するように思う。
まとめ
Raspberry Piとプロコンに連射機能を搭載することができた。
自分でプログラムを書く必要がある。コントローラーの仕様をちょっと調べる必要がある。接続処理は不安定。まだまだ改良が必要。
追記
-
category:
- 日記