Joycon-rs の使い方 - Joy-Conの検出

Posted on
Rust JoyCon Joycon-rs Joycon-rs-v0.3.1 NintendoSwitch

joycon_rs_logo (本記事はv0.3.1時点でのものである)

TL;DR

examples/scan_for_joycons.rs

Joycon-rsを使ってJoycon-rsを検出する

Joycon-rsの機能を用いて, 現在パソコンに接続しているJoy-Con および 順次パソコンに新規接続してくるJoy-Con を検出することができる.

Joy-Conの管理において機能の主軸となるのはJoyConManagerである.
JoyConManagernew()された際にパソコンに接続されているJoy-Conを内部のテーブルに記録しておく. そして指定時間ごとにscan()を実行し, 内部のテーブルを更新する. またscan()の際, 新たに見つかったJoy-ConをReceiver越しに通知してくれる.

つべこべ言うのはここまでにして, まずはサンプルコードを示す.

use joycon_rs::prelude::*;
use std::convert::TryInto;
use std::ops::Deref;

fn main() -> JoyConResult<()> {
    // First, connect your Joy-Cons to your computer!

    let manager = JoyConManager::new()?;
    let (managed_devices, new_devices) = {
        let lock = manager.lock();
        match lock {
            Ok(m) => (m.managed_devices(), m.new_devices()),
            Err(_) => unreachable!(),
        }
    };

    managed_devices.into_iter()
        .chain(new_devices)
        .try_for_each::<_, JoyConResult<()>>(|d| {
            if let Ok(device) = d.lock() {
                let device: &HidDevice = device.deref().try_into()?;
                println!("{:?}", device.get_product_string()?);
            }
            Ok(())
        })?;

    Ok(())
}

let manager = JoyConManager::new()?;JoyConManagerのインスタンスを作っているのは言うまでもない.

ここで生成されるインスタンスはArc<Mutex<T>>で包まれているので,

let (managed_devices, new_devices) = {
    let lock = manager.lock();
    match lock {
        Ok(m) => (m.managed_devices(), m.new_devices()),
        Err(_) => unreachable!(),
    }
};

という形でロックを取得してから, 必要な値であるmanaged_devices, new_devicesを取り出している.
ArcMutexをご存知でなくても, いまは気にせず先に進んで問題ない.

さて, これで

  • 現在パソコンに接続されているJoy-Conのセット: managed_devices
  • 順次パソコンに接続されるJoy-Conの通知チャンネル: new_devices

が手元に揃ったので, これらに対する処理を実装しよう.

managed_devices.into_iter()
    .chain(new_devices)

このサンプルコードの肝といってもいいのがこの部分である. managed_devices.into_iter()して生成されたイテレータに, new_devices.chain(), つまり連結している. そうすることで, 現在把握しているJoy-Conと, これから接続しているJoy-Conに対する操作をまとめて記述できる.

ちなみに, このイテレータはloop{}ブロックのように振る舞い, 基本的に止まることがない. managed_devicesに束縛されていたJoyConDevice全部の処理を終えたのちは, new_devicesに束縛されていたReceiverに新たなJoyConDeviceがやってくるのを口を開けて待っている, そんな状態になる.
もちろん, ループから抜ける場合もある. 例えば, これに続くtry_for_each()の処理が失敗して, Errを返さねばならないときなどだ.

下処理が済んだので, 実際の処理を実装しよう.

.try_for_each::<_, JoyConResult<()>>(|d| {
    if let Ok(device) = d.lock() {
        let device: &HidDevice = device.deref().try_into()?;
        println!("{:?}", device.get_product_string()?);
    }
    Ok(())
})?;

取得したJoy-Conに対してプロダクト名を尋ねている. 返答があればその名前を標準出力に流し, 返答がない, あるいは切断状態であればErrを返す.

実行

Joy-Con (L)を接続している場合, 次のような表示になる.

$ cargo run --example scan_for_joycons
   Compiling joycon-rs v0.3.1 (/hoge/github/joycon-rs)
    Finished dev [unoptimized + debuginfo] target(s) in 3.74s
     Running `target/debug/examples/scan_for_joycons`
Some("Joy-Con (L)")

ここにJoy-Con (R)を接続すると,

Some("Joy-Con (R)")

という新規の表示が出るはずだ. JoyConManagerのデフォルトのスキャン間隔は1秒なので, 多少は待つかもしれない。JoyConManager::with_interval()で, スキャン間隔の違うJoyConManagerを生成できる.

備考 / 余談

JoyConManagerのインスタンス数の制限

JoyConManagerはフィールドにhidapi::HidApiを持っており, その影響でインスタンスを一つしか作れない(2つ目以降を作るとJoyConManager::new()Errを返すはず).

このような場合にはシングルトンパターンを使うのが適切であろうから, 次回ぐらいのアップデートで修正したい. lazy_static の出番だろうか?

JoyConManager::new_devices()で生成されるReceiverについて

Receivercrossbeam_channelのmpmc, Multi-Producer Multi-Consumer 型のchannelのものである. いくらでもCloneできるが, メッセージはどれかひとつのReceiverrecv()されれば消える。

また, channelのキャパシティは0に設定されている。生成されているすべてのReceiverrecv()の状態になければ、送信されたメッセージはどこかに消える。

次回

次回は, 側面のLEDを制御する方法について解説する.

-> Next: Joycon-rs の使い方 - 側面LED, HOMEボタンのライト制御

Joycon-rs v0.3.1の解説記事一覧はこちら.
tags/joycon-rs-v0.3.1