「PlayStationコントローラからの入力データをobniz Board 1Yに表示」でPlayStationコントローラ「DUALSHOCK2」をパソコンに接続して、node.jsを用いてデータを入力しました。今回はGo言語を使って、PlayStationコントローラ「DUALSHOCK2」をRaspberry Pi 4に接続してデータを入力します。
- Raspbian:buster
- go言語のバージョン:go1.13.4
- コーディング環境:Visual Studio Code 1.44.2
各種ゲーム用コントローラーを扱うInput Subsystem
次のコマンドでディレクトリ「/dev/input」を見ると、event0とかevent1といったファイルがあります。このファイルがInput Subsystemを使う上で中心となるイベントデバイスファイルで、PlayStationコントローラ「DUALSHOCK2」に対応しています。
$ ls /dev/input by-id by-path event0 event1 js0 mice
イベントデバイスファイルを次のコマンドで読んでみます。
$ hexdump /dev/input/event1 0000000 5f78 6098 8fa3 000d 0003 0000 0061 0000 0000010 5f78 6098 8fa3 000d 0000 0000 0000 0000 0000020 5f78 6098 ddcc 000d 0003 0000 0060 0000 0000030 5f78 6098 ddcc 000d 0000 0000 0000 0000 0000040 5f79 6098 c0ea 000e 0003 0000 0061 0000 0000050 5f79 6098 c0ea 000e 0000 0000 0000 0000 0000060 5f79 6098 0b26 000f 0003 0000 0060 0000 0000070 5f79 6098 0b26 000f 0000 0000 0000 0000
この出力の形式は/usr/include/linux/input.hで次のように定められています。
struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value; };
それぞれの意味は次のようになります。
- time
- 入力した時間
- type
- キー入力やマウス入力などの入力タイプを示します。代表的な値は、
- EV_KEY:キーボードやマウスボタンなどのキー入力
EV_REL:マウスの動きやマウスホイールやジョグダイヤルなどの相対的な動き
EV_ABS:ジョイスティックの傾きやタッチパネルの入力やアクセルペダルなどの絶対的な動き - EV_KEY:キーボードやマウスボタンなどのキー入力
- code
- どのキーが入力されたかを示します。代表的な値は、
- KEY_SPACE:スペースキー
KEY_MUTE:ミュートキー
BTN_LEFT:マウスの左ボタン
BTN_GEAR_DOWN:ホイールコントローラーのギアダウン
REL_X:マウス等のx軸方向の動き
ABS_X:タッチパネル等のx軸方向の位置、ジョイスティックのx軸方向の傾き
ABS_GAS:ホイールコントローラーのアクセルペダル
注意: 例えば、エスケープキーを表すKEY_ESCとマウスなどのy軸方向の動きを表すREL_Yは、同じ0x01という値を持っているので、typeを見て判断します。 - KEY_SPACE:スペースキー
- value
- キーが押されたのか離したのか、マウスなどがどのぐらい動いたのかなど、typeによって次の意味を持ちます。
- type == EV_KEYの場合:キーを押したときは1、離したときは0、オートリピートによる入力があったときは2
type == EV_RELの場合:動いた量
type == EV_ABSの場合:現在の値 - type == EV_KEYの場合:キーを押したときは1、離したときは0、オートリピートによる入力があったときは2
イベントデバイスファイルを開いて、input_event構造体を読み込んで、その値に対応して処理を記述します。今回のアプリでは「gvalkov/golang-evdev」を使用して、input_event構造体を読み込んでいます。
ジョイスティック入力アプリの作成
ジョイスティック入力アプリを次に示します。「mattrasband/ps4」を参考に作成しました。
gops3.go
package main import ( "context" "fmt" "os" ) func main() { inputs, err := Discover() if err != nil { fmt.Printf("Error discovering controller: %s\n", err) os.Exit(1) } var device *Input for _, input := range inputs { if input.Type == Controller { device = input break } } ctx, cancel := context.WithCancel(context.Background()) defer cancel() events, _ := Watch(ctx, device) for e := range events { fmt.Printf("%+v\n", e) } }
- 33行目を使用するPlayStationコントローラ「DUALSHOCK2」のために変更しました。PS4のジョイスティックを使用する場合は、32行目のコメントを外してください。
ps4.go
package main import ( "context" "errors" "fmt" "regexp" "strings" ev "github.com/gvalkov/golang-evdev" ) type Type int //go:generate stringer -type=Type const ( _ Type = iota Controller MotionSensors Touchpad ) type Input struct { Device *ev.InputDevice Type Type } func Discover() ([]*Input, error) { // "Sony Interactive Entertainment" is only if wired // controllerRegex := regexp.MustCompile("(?:Sony Interactive Entertainment )?Wireless Controller") controllerRegex := regexp.MustCompile("Sony PLAYSTATION...3 Controller") candidates, err := ev.ListInputDevices("/dev/input/event*") if err != nil { return []*Input{}, err } inputs := []*Input{} for _, candidate := range candidates { name := strings.TrimSpace(controllerRegex.ReplaceAllString(candidate.Name, "")) fmt.Printf("*** %s *** %s *** %s\n", controllerRegex,candidate.Name,name) switch name { case "": inputs = append(inputs, &Input{ Device: candidate, Type: Controller, }) case "Motion Sensors": inputs = append(inputs, &Input{ Device: candidate, Type: MotionSensors, }) case "Touchpad": inputs = append(inputs, &Input{ Device: candidate, Type: Touchpad, }) default: fmt.Printf("Skipping: %s\n", candidate) } } if len(inputs) == 0 { return inputs, errors.New("unable to find any controller inputs, is it paired and on?") } return inputs, nil } type Button int //go:generate stringer -type=Button const ( // dpad DPadX Button = 16 DPadY Button = 17 // sticks LeftStickX Button = 0 LeftStickY Button = 1 LeftStickClick Button = 317 RightStickX Button = 3 RightStickY Button = 4 RightStickClick Button = 318 // triggers L1 Button = 310 L2Click Button = 312 L2 Button = 2 R1 Button = 311 R2Click Button = 313 R2 Button = 5 // aux Share Button = 314 Options Button = 315 Playstation Button = 316 // shapes Triangle Button = 307 Circle Button = 305 X Button = 304 Square Button = 308 // trackpad TODO //TrackpadX //TrackpadY //TrackpadClick // motion TODO ) type KeyState uint8 //go:generate stringer -type=KeyState const ( KeyUp KeyState = iota KeyDown ) // KeyEvent is a button press/release type KeyEvent struct { Event *ev.InputEvent Button Button State KeyState } // AbsEvent is an absolute value report (joysticks, lower triggers, or the d-pad - surprisingly) type AbsEvent struct { Event *ev.InputEvent Button Button Value int32 } func Watch(ctx context.Context, input *Input) (<-chan interface{}, error) { events := make(chan interface{}, 10) go func() { defer close(events) dev := input.Device fmt.Println("Running watcher...") for { event, err := dev.ReadOne() if err != nil { fmt.Printf("Unable to read one: %s\n", err) continue } if event.Type == 0 { continue } var e interface{} switch event.Type { case ev.EV_KEY: e = &KeyEvent{ Event: event, Button: Button(event.Code), State: KeyState(event.Value), } case ev.EV_ABS: e = &AbsEvent{ Event: event, Button: Button(event.Code), Value: event.Value, } case ev.EV_MSC: continue // only received when on USB, ignored since it adds nothing. default: fmt.Printf("Skipped: %+v\n", event) continue } select { case <-ctx.Done(): return case events <-e: default: } } }() return events, nil }
ジョイスティック入力アプリの実行
次のコマンドでコンパイルして実行します。PlayStationコントローラ「DUALSHOCK2」を操作することにより次のように入力データが表示されます。
$ go build $ ./gops3 *** Sony PLAYSTATION...3 Controller *** Sony PLAYSTATION(R)3 Controller Motion Sensors *** Motion Sensors *** Sony PLAYSTATION...3 Controller *** Sony PLAYSTATION(R)3 Controller *** Running watcher... &{Event:event at 1620546483.71385, code 304, type 01, val 01 Button:304 State:1} &{Event:event at 1620546483.270356, code 304, type 01, val 00 Button:304 State:0} &{Event:event at 1620546484.845495, code 305, type 01, val 01 Button:305 State:1} &{Event:event at 1620546485.64509, code 305, type 01, val 00 Button:305 State:0} &{Event:event at 1620546486.719608, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546486.759610, code 04, type 03, val 110 Button:4 Value:110} &{Event:event at 1620546486.978624, code 04, type 03, val 128 Button:4 Value:128} &{Event:event at 1620546486.998631, code 03, type 03, val 145 Button:3 Value:145} &{Event:event at 1620546487.58632, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546487.78634, code 03, type 03, val 145 Button:3 Value:145} &{Event:event at 1620546487.117630, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546487.137637, code 03, type 03, val 145 Button:3 Value:145} &{Event:event at 1620546487.177635, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546487.217638, code 03, type 03, val 145 Button:3 Value:145} &{Event:event at 1620546487.237643, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546487.277642, code 03, type 03, val 145 Button:3 Value:145} &{Event:event at 1620546487.297647, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546487.416654, code 03, type 03, val 145 Button:3 Value:145} &{Event:event at 1620546487.436656, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546487.516660, code 03, type 03, val 145 Button:3 Value:145} &{Event:event at 1620546487.556665, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546487.576663, code 03, type 03, val 145 Button:3 Value:145} &{Event:event at 1620546487.675670, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546487.715671, code 03, type 03, val 145 Button:3 Value:145} &{Event:event at 1620546487.735678, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546487.775675, code 03, type 03, val 145 Button:3 Value:145} &{Event:event at 1620546487.795678, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546487.835679, code 03, type 03, val 145 Button:3 Value:145} &{Event:event at 1620546487.855682, code 03, type 03, val 144 Button:3 Value:144} &{Event:event at 1620546487.955688, code 03, type 03, val 145 Button:3 Value:145}