How to read and write joystick(gamepad) events(inputs) - 조이스틱(게임패드) 이벤트 읽고 쓰기
Linux 2021. 7. 12. 22:32 |Python-evdev를 사용하면 리눅스 시스템에 연결되어 있는(/dev/input/) 조이스틱이나 게임패드를 조작할 때 발생하는 커널의 이벤트(입력)를 사용자 공간(Userspace)으로 보낼 수 있다.
조이스틱 뿐 아니라 마우스, 키보드, 터치스크린등의 이벤트를 읽고 쓸 수 있다.
evdev를 설치하자.

$ sudo pip3 install evdev    # available globally 
$ pip3 install --user evdev  # available to the current user

evdev와 함께 설치되는 입력 기기 모니터링 프로그램을 실행하고 게임패드를 조작해 보자.

게임패드의 버튼을 누를때마다 어떤 버튼이 눌렸는지 확인 할 수 있다.
각 키에 설정된 상수는 아래와 같다.
Up - EV_ABS, ABS_Y, PRESS: 0, RELEASE: 127 
Down - EV_ABS, ABS_Y, PRESS: 255, RELEASE: 127 
Right - EV_ABS, ABS_X, PRESS: 255, RELEASE: 127 
Left - EV_ABS, ABS_X, PRESS: 0, RELEASE: 127 
X - EV_KEY, BTN_JOYSTICK or BTN_TRIGGER (288) 
Y - EV_KEY, BTN_TOP (291) 
A - EV_KEY, BTN_THUMB (289) 
B - EV_KEY, BTN_THUMB2 (290) 
L - EV_KEY, BTN_TOP2 (292) 
R - EV_KEY, BTN_PINKIE (293) 
SELECT - EV_KEY, BTN_BASE3 (296) 
START - EV_KEY, BTN_BASE4 (297)
(VALUE - PRESS: 1, RELEASE: 0)
위 정보를 바탕으로 어떤 버튼을 눌렀는지 좀 더 쉽게 알 수 있는 프로그램을 만들어 보자.
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
65 
66 
67 
 | 
 import sys 
import evdev 
from evdev import InputDevice, categorize, ecodes 
devices = [evdev.InputDevice(path) for path in evdev.list_devices()] 
count = 0 
for device in devices: 
    print(device.path, device.name, device.phys) 
    if device.name.startswith('usb gamepad'): 
        gamepad = InputDevice(device.path) 
        count = 1 
        break 
if count < 1: 
    print("No usb gamepad found.") 
    sys.exit() 
aBtn = 289 
bBtn = 290 
xBtn = 288 
yBtn = 291 
lBtn = 292 
rBtn = 293 
selBtn = 296 
staBtn = 297 
for event in gamepad.read_loop(): 
    if event.type == ecodes.EV_KEY: 
        #print(event) 
        if event.value == 1:    #if pressed. 
            if event.code == aBtn: 
                print("A Button Pressed.") 
            elif event.code == bBtn: 
                print("B Button Pressed.") 
            elif event.code == xBtn: 
                print("X Button Pressed.") 
            elif event.code == yBtn: 
                print("Y Button Pressed.") 
            elif event.code == lBtn: 
                print("L Button Pressed.") 
            elif event.code == rBtn: 
                print("R Button Pressed.") 
            elif event.code == selBtn: 
                print("Select Button Pressed.") 
            elif event.code == staBtn: 
                print("Start Button Pressed.") 
        elif event.value == 0:    #if released. 
            print("Button Released.")     
    elif event.type == ecodes.EV_ABS: 
        absevent = categorize(event) 
        #print(ecodes.bytype[absevent.event.type][absevent.event.code], absevent.event.value) 
        if ecodes.bytype[absevent.event.type][absevent.event.code] == "ABS_X": 
            if absevent.event.value == 0: 
                print("Left.") 
            elif absevent.event.value == 255: 
                print("Right.") 
            elif absevent.event.value == 127: 
                print("Center.") 
        elif ecodes.bytype[absevent.event.type][absevent.event.code] == "ABS_Y": 
            if absevent.event.value == 0: 
                print("Up.") 
            elif absevent.event.value == 255: 
                print("Down.") 
            elif absevent.event.value == 127: 
                print("Center.") 
 | 

이번엔 특정 버튼을 누르는 프로그램을 만들어 보자.
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
 | 
 import sys, time 
import evdev 
from evdev import InputDevice, ecodes as e 
devices = [evdev.InputDevice(path) for path in evdev.list_devices()] 
count = 0 
for device in devices: 
    print(device.path, device.name, device.phys) 
    if device.name.startswith('usb gamepad'): 
        dev = InputDevice(device.path) 
        count = 1 
        break 
if count < 1: 
    print("No usb gamepad found.") 
    sys.exit() 
time.sleep(10) 
''' 
# Jump 
dev.write(e.EV_ABS, e.ABS_X, 255) 
dev.write(e.EV_ABS, e.ABS_Y, 0) 
dev.write(e.EV_SYN, 0, 0) 
time.sleep(0.1) 
dev.write(e.EV_ABS, e.ABS_X, 127) 
dev.write(e.EV_ABS, e.ABS_Y, 127) 
dev.write(e.EV_SYN, 0, 0) 
time.sleep(0.1) 
''' 
''' 
# Button Keep Hit 
while True: 
    dev.write(e.EV_KEY, e.BTN_THUMB2, 1) 
    dev.write(e.EV_SYN, 0, 0) 
    time.sleep(0.1) 
    dev.write(e.EV_KEY, e.BTN_THUMB2, 0) 
    dev.write(e.EV_SYN, 0, 0) 
    time.sleep(0.1) 
''' 
# Key Holding for 1 sec 
dev.write(e.EV_ABS, e.ABS_Y, 255) 
dev.write(e.EV_SYN, 0, 0) 
time.sleep(1) 
dev.write(e.EV_ABS, e.ABS_Y, 127) 
dev.write(e.EV_SYN, 0, 0) 
dev.close() 
 | 
일반적인 게임에서 '앞으로 점프', '버튼 연타', '1초간 누르고 있기' 등의 동작이 정의되었다.
조금 더 복잡한 동작을 프로그래밍 해 보자.
스트리트 파이터 2의 류 필살기인 승룡권은 아래와 같은 커맨드로 이루어져 있다.
→ + ↘ + ↓ + ↘ (→ + ↓ + ↘)
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
63 
64 
 | 
 import sys, time 
import evdev 
from evdev import InputDevice, ecodes as e 
devices = [evdev.InputDevice(path) for path in evdev.list_devices()] 
count = 0 
for device in devices: 
    print(device.path, device.name, device.phys) 
    if device.name.startswith('usb gamepad'): 
        gamepad = InputDevice(device.path) 
        count = 1 
        break 
if count < 1: 
    print("No usb gamepad found.") 
    sys.exit() 
# read_loop() 
# 모든 키 입력을 하나도 빼지 않고 읽어 온다. (이 루프에서 write() 되는 키 입력 포함) 
# 그러므로 이 루프내에서 같은키를 두 번 이상 쓰면 무한루프에 걸린다. 
for event in gamepad.read_loop(): 
    if event.type == e.EV_KEY: 
        if event.value == 1 and event.code == e.BTN_TOP: 
            # Shoryuken 
            # + Right (→) 
            gamepad.write(e.EV_ABS, e.ABS_X, 255) 
            gamepad.write(e.EV_SYN, 0, 0) 
            time.sleep(0.05) 
            # + Down (↘) 
            gamepad.write(e.EV_ABS, e.ABS_Y, 255) 
            gamepad.write(e.EV_SYN, 0, 0) 
            time.sleep(0.05) 
            # - Right (↓|) 
            gamepad.write(e.EV_ABS, e.ABS_X, 127) 
            gamepad.write(e.EV_SYN, 0, 0) 
            time.sleep(0.05) 
            # + Right (↘) 
            gamepad.write(e.EV_ABS, e.ABS_X, 255) 
            gamepad.write(e.EV_SYN, 0, 0) 
            time.sleep(0.05) 
            # Punch Press 
            gamepad.write(e.EV_KEY, e.BTN_JOYSTICK, 1) 
            gamepad.write(e.EV_SYN, 0, 0) 
            time.sleep(0.05) 
            # Punch Release 
            gamepad.write(e.EV_KEY, e.BTN_JOYSTICK, 0) 
            gamepad.write(e.EV_SYN, 0, 0) 
            time.sleep(0.05) 
            # - Right, - Down = Center 
            gamepad.write(e.EV_ABS, e.ABS_X, 127) 
            gamepad.write(e.EV_ABS, e.ABS_Y, 127) 
            gamepad.write(e.EV_SYN, 0, 0) 
            time.sleep(0.05) 
dev.close() 
 | 
위 코드를 실행하고 스트리트 파이터 2를 실행한다. 류나 켄을 선택하고 Y버튼을 누르면 작은 손 공격이 한 번 나간후 승룡권이 나간다.
버튼이 많은 컨트롤러를 사용한다면 사용하지 않는 버튼을 24번째 줄 event.code에 지정해주자.
윈도우에서는 HID 라이브러리를 이용하자.
'Linux' 카테고리의 다른 글
| Linux(Ubuntu) Build Your Own Web Server - 리눅스(우분투)로 웹서버 만들기 (0) | 2021.08.25 | 
|---|---|
| Linux(Ubuntu) Static IP Configuration - 리눅스(우분투) 고정 IP (0) | 2021.08.25 | 
| Dynamic Shared Object(Library) in Linux - 리눅스 동적 공유 라이브러리 (0) | 2021.02.09 | 
| Linux CMake를 이용한 컴파일(빌드) 자동화 (0) | 2021.02.08 | 
| Linux make를 이용한 컴파일(빌드) 자동화 (0) | 2021.02.07 |