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:ジョイスティックの傾きやタッチパネルの入力やアクセルペダルなどの絶対的な動き

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を見て判断します。

value
キーが押されたのか離したのか、マウスなどがどのぐらい動いたのかなど、typeによって次の意味を持ちます。

type == EV_KEYの場合:キーを押したときは1、離したときは0、オートリピートによる入力があったときは2
type == EV_RELの場合:動いた量
type == EV_ABSの場合:現在の値

イベントデバイスファイルを開いて、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}