この記事は?
タイトルの通り、MATLAB から Nintendo Switch の Joy-Con に接続する方法の解説記事です。
週末 Switch で遊んでいたら、ふと
「Switch の Joy-Con の入力を MATLAB で使えないかな」
と思い立っていろいろ試してみたところ、意外と MATLAB からでも簡単に接続できたので、接続方法について記事にまとめてみました。
PC と Joy-Con の接続
MATLAB は PC 上で動きますので、まずは Joy-Con と PC を接続する必要があります。が、これはめちゃくちゃ簡単で、普通の Bluetooth デバイスとほとんど変わりません。Joy-Con の真ん中の接続ボタンを押すと PC の Bluetooth デバイスリストに Joy-Con が表示されるので、それを選べば終わりです。
MATLAB で Joy-Con に接続
PC と Joy-Con が接続できたので、今度は MATLAB 上から Joy-Con に接続します。
他言語で Joy-Con へ接続している人たちの記事を見てみると、ほとんどの方が hidapi という API を用いてアクセスしていました。Joy-Con の Bluetooth 接続のプロファイルは HID なので、hid 用の汎用ライブラリが使えるというわけですね。
この hidapi は c で作られた dll なので、loadlibrary 関数を使って読み込んで calllib 関数で適宜 dll 内の関数を実行すれば、他の言語と同様に MATLAB でも動かせそうです。
ただ、calllib 関数で dll の関数を呼び出すと、データ型の問題が結構ネックになりがちです。特に入出力にポインタ型が絡むと一気に難易度が上がるイメージがあり、空いた時間にちょっと手を出したが最後、底なし沼から抜け出せなくなる光景が容易に目に浮かびます。
「誰か似たようなことやってないかなぁ……」
と思ってダメ元で調べてみると、
ありました(マジか……)。
MATLAB で hidapi を使うためのクラスを作ってくれています。これを Switch の Joy-Con 用に改造すれば簡単に接続できそうです。
早速 zip 形式でダウンロードして解凍します。
hidapi.m で定義されているクラスが接続用のオブジェクトみたいです。ちなみに、この zip ファイルには hidapi そのものも一緒に入っているので、hidapi を別途ダウンロードする必要はありません。
hidapi を使って Joy-Con が認識されているか確認する
まずは hidapi で Joy-Con が正しく認識できているか確認していきましょう。
function hid = hidapi(debug,vendorID,productID,nReadBuffer,nWriteBuffer)
hidapi の初期化には 5 つの入力引数が必要です。debug はデバッグモードで動かすかどうかを指定するもので、今回は 0 でよさそうです。nReadBuffer, nWriteBuffer は Joy-Con との通信データのバッファサイズですので、これはどちらも 0x40 = 64 に設定します(たぶん合ってるはず)。問題は vendorID と productID なのですが、これらは USB ID Database から確認できます。今回は Joy-Con(L) を接続しているので、
vendorID : 0x057E = 1406
productID : 0x2006 = 8198
となります。早速これらを用いて hidapi クラスを定義します。
>> hid = hidapi(0,1406,8198,64,64)
hid =
hidapi のプロパティ:
handle: []
debug: 0
vendorID: 1406
productID: 8198
nReadBuffer: 64
nWriteBuffer: 64
slib: 'hidapiusb'
sheader: 'hidapi.h'
isOpen: 0
slib プロパティが "hidapiusb" という USB 接続っぽい名前になっていますが、これはクラスの内部でライブラリを呼び出すためのエイリアスなので、別にこのままで問題ありません。
早速この hidapi オブジェクトを使って Joy-Con(L) が認識されているか確認します。接続デバイスの確認は hidapi の enumerate メソッドで行います。ただし、この enumerate メソッドは一か所バグがあり、事前に修正しておく必要があります。397行目を以下のように修正します。
修正前:
str = calllib(u.slib,'hid_enumerate',uint16(vendorID),uint16(productID));
修正後:
str = calllib(hid.slib,'hid_enumerate',uint16(vendorID),uint16(productID));
(入力引数のところに突然 u という変数が出てきてしまっているので、正しい変数名 hid に修正しただけです)
修正が終わったら、enumerate メソッドを使ってみます。
>> str = hid.enumerate(1406,8198)
str =
libpointer
enumerate メソッドは、内部で dll 内の hid_enumerate 関数を calllib で呼び出しています。hid_enumerate 関数の返り値はポインタ型なので、実行結果は libpointer 型で返ってきます。libpointer の中身を確認する場合は、Value メソッドを使います。
>> str.Value
>> str.Value
ans =
フィールドをもつ struct:
path: '\\?\hid#{00001124-0000-1000-8000-00805f9b34fb}_vid&0002057e_pid&2006#8&2106208a&1&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}'
vendor_id: 1406
product_id: 8198
serial_number: [1×1 lib.pointer]
release_number: 0
manufacturer_string: [1×1 lib.pointer]
product_string: [1×1 lib.pointer]
usage_page: 1
usage: 5
interface_number: -1
next: []
Joy-Con(L)のデバイス情報が取得できています。問題なく認識できているみたいですね。
それでは open メソッドを使って通信を開始しましょう。
>> hid.open
何も表示されなければ接続成功です。
※dll 側のエラー処理が不十分なようで、デバイスが認識できない状態で open メソッドを実行すると MATLAB がクラッシュします。必ず enumerate メソッドでデバイスが認識されていることを確認してから open を実行しましょう。
Joy-Con のランプを点灯する
次に、Joy-Con にランプを点灯させる指令を送ってみます。
Joy-Con には 64 バイトのデータを送る必要があります。送るデータの形式は以下の通りです。
1バイト目:レポート ID
2バイト目:カウンター
11バイト目:サブコマンド
12バイト目:データ
その他:0
レポート ID については通常 1 に設定しておけば問題ないようです。カウンターは指令を送るたびにインクリメントした 0x0-0xF の範囲の値です。サブコマンドは指令の種類を決める値です。ランプを点灯させる場合、サブコマンドは 0x30(= 48)となります。データは、具体的な指令内容を表す設定値です。ランプを点灯させる場合、下 4 bit が 4 つのランプの点灯、上 4 bit が 4 つのランプの点滅を表す 8bit の値になります。例えば 7 という値を入れると、7 = 0b00000111 ですので、上から 3 つのランプが点灯します。
データを作ったら、あとはこれを Joy-Con に送るだけです。hidapi オブジェクトには初めからデータを送る用のメソッド write が実装されているのですが、ちょっと引数として与えるデータの形式が Joy-Con 用には使いづらいので、新たに sendSubCommand メソッドを定義してみました。
function sendSubCommand(hid,command,data,gcount)
len = length(data);
buf = zeros(1,hid.nWriteBuffer);
buf(1) = 1;
buf(2) = gcount;
buf(11) = command;
buf(12:12+len-1) = data;
pbuffer = libpointer('uint8Ptr', uint8(buf));
[res,h] = calllib(hid.slib,'hid_write',hid.handle,pbuffer,uint64(length(buf)));
end
1行64列のデータを作り、libpointer 関数で hid_write に渡すための uint8 型のポインタに変換し、calllib で hid_write を呼び出しているだけです。うまく指令が送れていれば、Joy-Con(L) の上から3つのランプが点灯するはずです。
hid.sendSubCommand(48,7,0)
やったぜ!!!
Joy-Con(L) のボタン入力を取得する
Joy-Con(L) へのデータの送信ができたので、今度は Joy-Con(L) からデータを受信してみます。今回はボタンの入力情報を取得してみましょう。
Joy-Con(L) はデフォルトではボタンや IMU などのセンサの情報を送信していないようで(少なくとも私の Joy-Con はデフォルトではボタンのデータを送信してませんでした)、まずデータを送信してくれるように指令を送る必要があります。
データを送信してもらうためには、サブコマンド 0x03(=3)、データ 0x30(=48) を指令値として送ります。
hid.sendSubCommand(3,48,1)
これで、あとは read メソッドを使えば Joy-Con のデータを受け取ることができます。左の Joy-Con のボタンのデータは 6 バイト目です。
>> rmsg = hid.read;
>> rmsg(6)
ans =
uint8
0
何もボタンを押してないので 0 が返ってきました。返ってくる値は、下 4bit に「左右上下」の順番で 0 or 1 を並べた値です。例えば上のボタンを押しながら同じコードを実行すると、
>> rmsg = hid.read;
>> rmsg(6)
ans =
uint8
2
2(= 0b0010)が返ってきました(少しラグがあるようで、ボタンを押し始めてすぐに実行すると取得できない場合があります)。
もちろん同時押しにも対応しています。例えば全部のボタンを同時押しすると、
>> rmsg = hid.read;
>> rmsg(6)
ans =
uint8
15
15(= 0b1111)が返ってきました!
せっかくなので figure 上に押されているボタンを表示するサンプルスクリプトを作ってみました。
f = figure;
recs = gobjects(1,4);
recs(1) = rectangle('Position',[-1,-3,2 2],'Curvature',[1 1]); % 下
recs(2) = rectangle('Position',[-1,1,2 2],'Curvature',[1 1]); % 上
recs(3) = rectangle('Position',[1,-1,2 2],'Curvature',[1 1]); % 右
recs(4) = rectangle('Position',[-3,-1,2 2],'Curvature',[1 1]); % 左
axis equal
%%
while isgraphics(f)
rmsg = hid.read;
buttonL = rmsg(6);
for i = 1:4
value = bitget(buttonL,i);
if value == 1
recs(i).FaceColor = 'r';
else
recs(i).FaceColor = 'none';
end
end
drawnow;
end
とてもいい感じに動いてますね!!
参考資料
今回の記事を書くにあたり、以下のページを参考にさせていただきました。
SwitchのJoyConをプログラムから使う
https://www.amusement-creators.info/articles/advent_calendar/2019/23_0/
Joy-ConにPythonからBluetooth接続をして6軸センサーと入力情報を取得する
https://qiita.com/tocoteron/items/9a5d81c8f640ecaff7a9
GitHub - dekuNukem/Nintendo_Switch_Reverse_Engineering
https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
終わりに
ということで、無事 MATLAB から Joy-Con に接続することができました。今回はボタン入力の取得まででしたが、IMU センサの値などを使えばもっと面白いことができそうな気がするので、次回は Joy-Con の IMU センサを使って色々遊ぶ記事を書きたいと思います。
LGTM がつくと次回の記事が豪華になるかも。