ITS's Dev Story

OpenBCI로 마인크래프트의 스티브를 움직이는 방법을 알아보겠다. 



OpenBCI 소프트웨어의 최신 버전은 OSC 통신을 지원, Focus 감지 기능을 지원하므로, 이를 이용하면 스티브를 앞으로 움직이기 위해 키보드로 'W' 키를 누르는 과정을 파이썬 소프트웨어로 대체할 수 있다.


1. Python 3.6 버전을 다운받는다. (2.7 버전 안됨! +_+)

 * 이미 파이썬 2.7 버전을 설치한 경우에는 2.7 버전을 지우고, 3.6 설치, 환경변수 재설정 등 각종 설정을 다시 해야 한다. 

 * 다운로드 링크 : https://www.python.org/downloads/release/python-362/


2. Muselab 다운로드

 - Muselab은 OSC 데이터 분석 등에 유용하게 사용되므로, 다운받을 필요가 있다. 다운받지 않아도 프로그램을 동작하는데 문제는 없지만, 신호를 분석할 때에는 필요가 있다.

 * 다운로드 링크 : https://storage.googleapis.com/ix_downloads/musesdk-3.4.1/musesdk-3.4.1-windows-installer.exe


3. OpenBCI S/W 실행, OSC 프로토콜 설정 및 Focus 측정



OpenBCI에서 위와 같이 Control Panel 설정을 변경한다. (설치 및 실행 관련은 전 포스팅에서 설명했으므로 생략한다.)


[Networking] - 프로토콜 OSC로 변경, Data Type은 Focus, IP (127.0.0.1) Port(12345) 변경



위는 Focus Widget인데, focus를 측정하는 소스코드는 아래와 같다.


<W_focus.pde> 소스코드 중 일부


float alpha_avg = 0, beta_avg = 0; boolean isFocused; float alpha_thresh = 0.7, beta_thresh = 0.7, alpha_upper = 2;

<중략>


void update()

{


<중략>

alpha_avg = alpha_avg / alpha_count; beta_avg = beta_avg / beta_count; if (alpha_avg > alpha_thresh && alpha_avg < alpha_upper && beta_avg < alpha_thresh)

{ isFocused = true; }

else

{ isFocused = false; }

}

if (alpha_avg > 0.7 && alpha_avg < 2 && beta_avg < 0.7)


조건에 걸리는 경우, isFocused = true 를 OSC로 보낸다.


4. 제공하는 Python 소스코드를 이용해서, 마무리한다.


아래 소스코드를 SerialFocusAdvanced.py 로 저장하고 실행한다. 없는 라이브러리는 pip을 이용해 받아 준다.

import argparse
import ctypes
from ctypes import wintypes
import time

user32 = ctypes.WinDLL('user32', use_last_error=True)

INPUT_MOUSE    = 0
INPUT_KEYBOARD = 1
INPUT_HARDWARE = 2

KEYEVENTF_EXTENDEDKEY = 0x0001
KEYEVENTF_KEYUP       = 0x0002
KEYEVENTF_UNICODE     = 0x0004
KEYEVENTF_SCANCODE    = 0x0008

MAPVK_VK_TO_VSC = 0

# msdn.microsoft.com/en-us/library/dd375731
VK_TAB  = 0x09
VK_MENU = 0x12
VK_W = 87

# C struct definitions

wintypes.ULONG_PTR = wintypes.WPARAM

class MOUSEINPUT(ctypes.Structure):
    _fields_ = (("dx",          wintypes.LONG),
                ("dy",          wintypes.LONG),
                ("mouseData",   wintypes.DWORD),
                ("dwFlags",     wintypes.DWORD),
                ("time",        wintypes.DWORD),
                ("dwExtraInfo", wintypes.ULONG_PTR))

class KEYBDINPUT(ctypes.Structure):
    _fields_ = (("wVk",         wintypes.WORD),
                ("wScan",       wintypes.WORD),
                ("dwFlags",     wintypes.DWORD),
                ("time",        wintypes.DWORD),
                ("dwExtraInfo", wintypes.ULONG_PTR))

    def __init__(self, *args, **kwds):
        super(KEYBDINPUT, self).__init__(*args, **kwds)
        # some programs use the scan code even if KEYEVENTF_SCANCODE
        # isn't set in dwFflags, so attempt to map the correct code.
        if not self.dwFlags & KEYEVENTF_UNICODE:
            self.wScan = user32.MapVirtualKeyExW(self.wVk,
                                                 MAPVK_VK_TO_VSC, 0)

class HARDWAREINPUT(ctypes.Structure):
    _fields_ = (("uMsg",    wintypes.DWORD),
                ("wParamL", wintypes.WORD),
                ("wParamH", wintypes.WORD))

class INPUT(ctypes.Structure):
    class _INPUT(ctypes.Union):
        _fields_ = (("ki", KEYBDINPUT),
                    ("mi", MOUSEINPUT),
                    ("hi", HARDWAREINPUT))
    _anonymous_ = ("_input",)
    _fields_ = (("type",   wintypes.DWORD),
                ("_input", _INPUT))

LPINPUT = ctypes.POINTER(INPUT)

def _check_count(result, func, args):
    if result == 0:
        raise ctypes.WinError(ctypes.get_last_error())
    return args

user32.SendInput.errcheck = _check_count
user32.SendInput.argtypes = (wintypes.UINT, # nInputs
                             LPINPUT,       # pInputs
                             ctypes.c_int)  # cbSize

# Functions

def PressKey(hexKeyCode):
    x = INPUT(type=INPUT_KEYBOARD,
              ki=KEYBDINPUT(wVk=hexKeyCode))
    user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))

def ReleaseKey(hexKeyCode):
    x = INPUT(type=INPUT_KEYBOARD,
              ki=KEYBDINPUT(wVk=hexKeyCode,
                            dwFlags=KEYEVENTF_KEYUP))
    user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))

import argparse
import math
import time

from pythonosc import dispatcher
from pythonosc import osc_server

def eeg_handler1(unused_addr, args, ch1):
    print("Blink: ", ch1)

def eeg_handler2(unused_addr, args, ch1):
    print("Jaw Clench: ", ch1)

def eeg_handler3(unused_addr, args, ch1):
    print("Open Bci Focus: ", ch1)
    if(ch1):
        PressKey(VK_W)
        time.sleep(0.25)
        ReleaseKey(VK_W)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--ip", default="0.0.0.0", help="The ip to listen on")
    parser.add_argument("--port", type=int, default=12345, help="The port to listen on")
    args = parser.parse_args()

    dispatcher = dispatcher.Dispatcher()
    dispatcher.map("/debug", print)
    dispatcher.map("/muse/elements/blink", eeg_handler1, "Blink")
    dispatcher.map("/muse/elements/jaw_clench", eeg_handler2, "Jaw_clench")
    dispatcher.map("/openbci", eeg_handler3, "Jaw_clench")

    server = osc_server.ThreadingOSCUDPServer((args.ip, args.port), dispatcher)
    print("Serving on {}".format(server.server_address))
    server.serve_forever()


4. 마인크래프트를 실행, 스티브를 움직여 본다.