GoでJoyconをポインタデバイスにしてみた


こんにちは、最近ニンテンドースイッチにハマってるNoboNoboです。 ニンテンドースイッチ世界総販売台数1000万台突破おめでとうございます。

今回の記事はGoアドベントカレンダー2017 その3の14日目の記事です。

ニンテンドースイッチに付属するJoyconをGoで読み取りしたらできたのでその解説を書きます。

また @shibu_jp 氏のgotomationにてプレゼン用ポインタとして動作させるところまで。

Joyconのペアリング

JoyconはBluetoothでPCと接続できます。 一般的なデバイスと同様の手続きで接続確立できます。

注意点としてはJoyconを通電中のニンテンドースイッチに差し込むだけでペアリングセッションはニンテンドースイッチ側に上書きされる事です。この後PCで使うにはペアリングからやり直しが必要です。 Joyconのペアリング開始はConnect長押しです。

connectボタン

接続は出来ますが、標準におまかせするとなにやらデジタルゲームパッド相当でしか認識できないのです。 ならばということでもう少し低レイヤーに降りてみます。

hidパッケージ

JoyconはPC上でHIDプロファイルのデバイスとして認識されますので、HIDの範疇で済めばより低いUSB接続やBluetooth接続レイヤーの事は無視できます。

あとはGoでhidとお話しするには? hidサポートパッケージはflynnのを使ってみました。試せてないけどWin64サポートもあるのでこれを選びました。

go get -u https://github.com/flynn/hid

Joyconアクセスライブラリ

素晴らしいドキュメントをもとに、 Joyconへ簡単にアクセスするユーティリティライブラリを自作しました。

MITライセンスで公開: https://github.com/nobonobo/joycon

go get -u github.com/nobonobo/joycon

実装済の機能は

  • 生データをバイブレーション機能に送信できる
  • キャリブレーションされたアナログスティックの読み取り
  • ボタン状態の読み取り
  • 6軸センサ(ジャイロと加速度センサ)の読み取り

実装できてない機能

  • 高度なバイブレーション機能
  • デッドゾーンやジャイロセンサ情報のキャリブレーション
  • プレイヤーLEDやホームボタンLEDの制御
  • IRセンサーの制御

使い方

joycon.Search()にてサポートしてる接続済のデバイス情報一覧が得られます。 以下のサンプルは見つけた最初のデバイスを利用します。

得られたデバイスの「.Path」にはユニークな識別文字列が入っています。 これを元にjoycon.NewJoycon(識別文字列)にてJoyconインスタンスを作ります。

うまくいけば、あとは裏でgoroutineがデバイスとの通信を行なってくれます。

あとは、<-jc.State()にてアナログスティックやバッテリー、ボタンステータスが得られます。 <-jc.Sensor()にて6軸センサステータスが得られます。 Joycon.Close()すればgoroutineは止まります。

$GOPATH/github.com/nobonobo/joycon/cmd/simple/main.go

package main

import (
    "fmt"
    "log"

    "github.com/nobonobo/joycon"
)

func main() {
    devices, err := joycon.Search()
    if err != nil {
        log.Fatalln(err)
    }
    if len(devices) == 0 {
        log.Fatalln("joycon not found")
    }
    jc, err := joycon.NewJoycon(devices[0].Path)
    if err != nil {
        log.Fatalln(err)
    }
    s := <-jc.State()
    fmt.Printf("%#v\n", s.Buttons)  // Button bits
    fmt.Printf("%#v\n", s.LeftAdj)  // Left Analog Stick State
    fmt.Printf("%#v\n", s.RightAdj) // Right Analog Stick State
    a := <-jc.Sensor()
    fmt.Printf("%#v\n", a.Accel) // Acceleration Sensor State
    fmt.Printf("%#v\n", a.Gyro)  // Gyro Sensor State

    jc.Close()
}

実行例

$ cd $GOPATH/github.com/nobonobo/joycon/cmd/simple
$ go run main.go
0x8f
joycon.Vec2{X:0, Y:0}
joycon.Vec2{X:0, Y:-0.02037201}
joycon.Vec3{X:0.46362305, Y:0.2944336, Z:-0.85058594}
joycon.Vec3{X:-0.0029296875, Y:0.00390625, Z:-0.03149414}

センサー情報がどの軸のものかはここに図があります(Great!)

プレゼンポインターを作る

上記のライブラリでJoyconの機能にアクセスできるようになりました。 次はJoyconの操作情報からマウスやキーボードイベントの送信をやって見ます。

shibukawa(shibu_jp)氏の作った gotomation が メジャーデスクトップに対応していて使い方もシンプルなのでオススメです。

go get github.com/nobonobo/joycon/cmd/pointer

あらかじめJoycon( R )をPCにペアリングしておきます。 これで、$GOPATH/bin/pointerを起動すると・・・

$ pointer
22:11:05.252595 connected: Bluetooth_057e_2007_XXXXXXXX
22:11:05.769333 battery: 100 %

上記のような表示のあと、

  • Joycon( R )を振り回すとマウスポインタが動きます。
  • Yボタン: 左クリック
  • Bボタン: 右クリック
  • Xボタン: 中クリック
  • +ボタン: ESCキー
  • スティック: カーソルキー
  • SL/SR: マウススクロールホイール

また、クリック操作に対しバイブレーションフィードバックがあります。

夏に行われた 「Umeda.go #2 + KUG2」 のイベントで実際に登壇して使いました。

動作サンプル

課題

  • マッピングがハードコードをやめたい
  • キーにマップしたらキーリピート機能が欲しい
  • IRセンサー使って見たい
  • WindowsやLinuxでの動作確認
  • スポットライトだせば良かったかも?

継続型実装パターンの紹介

上記のpointerのコードでも使っていますが、 継続動作するような実装を書くときのパターンを紹介しておきます。

メイン関数のセットアップ処理が終わったら以下のような無限ループにします。

jc, err := joycon.NewJoycon(devPath)
if err != nil {
    log.Fatalln(err)
}
defer jc.Close()
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt)
for {
    select {
    case <-sig:
        return
    case s, ok := <-jc.State():
        if !ok {
            return
        }
        ...
    case s, ok := <-jc.Sensor():
        if !ok {
            return
        }
        ...
    }
}

Ctrl+Cを受け取ると、メイン関数を抜けようとしますが、 deferにてjc.Close()がよばれちゃんと後始末します。 無限ループ中はchanを通じてイベントを受けたり処理を呼んだりします。 デーモンサービスとして運用するならos.Interruptだけでなく、syscall.SIGHUPやsyscall.SIGTERMなどもsigに載せましょう。

signal.Notify(sig, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM)

ハードウェアハックについて

ガチ勢はC言語一択だしカジュアル勢はpythonかnodejsみたいな風潮ですが、 gobot.ioを始めGoもだいぶいろんな周辺ライブラリが揃い始めています。

ただしGoで組み込みソフトウェアはあくまでlinuxカーネルありきなんですよね。 まだまだマイコンレベルのファームをGoで書くのはツラミが多いです。

GoがポートされたFuchsiaが広まればあるいは、、、というところです。 でもlinuxカーネルの動く小さなPCはどんどん安くなってるのでどっちの普及が早いのだろう?

わかったこと

  • dekuNukem氏の解析情報があればHIDのレイヤでごにょごにょすればJoyconの一通りの機能にアクセスすることができました。Thank you so much for this great information!
  • また、gotomationを使うとデスクトップに対し簡単にキーやマウス操作イベントを送信できます。
  • Joyconの設計スゴイ
    • ジャイロセンサは200Hzサンプルで都度受け取るの負荷が大きいなぁと思ってたら3つをまとめてパケットに載せて解決してある。
    • アナログスティックの個体差は吸収に意外と苦労するんだけどしっかり対策してあった。
    • Joyconはレイテンシを保つためにBluetoothの往復の通信路一対にいろんなデータをコンパクトに詰めてあった。
  • その分読み取りしつつコマンドやバイブレーションデータを載せるというのは比較的面倒だった。
  • goroutineなかったらもう少し手こずっていたと思うし、ベストエフォートでデータ交換するの難しかっただろうと思う。
  • プレゼンにおけるジャイロセンサを用いたポインタ操作は意外と快適。この手の製品はまだお高いし選択肢が少ない?
  • プレゼン用にJoyconだけ買うというのもあり(かも?)

まとめ

ハードウェアハックにもGo言語いいよ!