???? - 1. 아두이노를 활용한 조이스틱 제작방법 요약

 

다들 집안 냉장고에 요리하다 남은 아두이노(Arduino)가 하나씩들 있으실텐데요. 이 놀고있는 아두이노를 활용해 조이스틱(게임 컨트롤러)를 만들 수 있는 라이브러리 및 특징에 대해 조사한 내용을 요약 정리해보았습니다.

 

 

1. UnoJoy

  • 사이트1 : https://github.com/AlanChatham/UnoJoy (2019/3/27 버전)
  • 사이트2 : https://code.google.com/archive/p/unojoy/ (2013/2/21 버전)
  • 아두이노 우노, 메가, 레오나르도 지원
  • PS3-Compatible 기기 제작이 목적이므로 지원되는 컨트롤 종류 및 갯수가 PS3 패드에서 지원하는 기능에 한정된다. 스로틀, 러더 컨트롤 등은 미지원.
  • 프로그래밍을 한 후에 조이스틱 장치로 인식시키기 위해 펌웨어를 변경해주는 별도의 작업이 필요하다.(다시 되돌릴수도 있다)(Java 32비트 버전만 지원함에 유의)
  • 개발 과정에서 펌웨어를 변경하지 않고 조이스틱 기능을 테스트할 수 있는 툴이 제공된다.

 

2. Arduino Joystick Library

  • 사이트 : https://github.com/MHeironimus/ArduinoJoystickLibrary
  • 아두이노 레오나르도만 지원
  • 다음과 같은 컨트롤을 지원한다. 하나의 아두이노로 최대 세개의 조이스틱으로 인식시킬 수도 있다.
      
     - Buttons (default: 32)
     - Up to 2 Hat Switches
     - X, Y, and/or Z Axis (up to 16-bit precision)
     - X, Y, and/or Z Axis Rotation (up to 16-bit precision)
     - Rudder (up to 16-bit precision)
     - Throttle (up to 16-bit precision)
     - Accelerator (up to 16-bit precision)
     - Brake (up to 16-bit precision)
     - Steering (up to 16-bit precision)
    
  • 아두이노 레오나르도는 업로딩만으로 USB 입력장치로 인식시킬 수 있는 기능을 자체적으로 지원하므로 별도의 펌웨어 변경은 필요없다.

사양 상으로는 후자 쪽이 더 훌륭해 보이는 것이 사실이지만, 냉동실에서 고인얼음과 친구먹고 있는 아두이노라면 높은 확률로 우노일 가능성이 높겠죠.

 

그럼, 알아본 내용을 바탕으로 ‘뭔가’를 향한 여정을 계속해 보도록 하겠습니다.

 

I will be back..

To be continued..

,

wxPython에서 별도의 어플리케이션 아이콘을 지정하지 않으면 기본 아이콘으로 표시된다. 이를 원하는 아이콘으로 지정하는 방법에 대해 정리해보았다.

  1. 프레임 창 좌측 상단에 표시되는 아이콘은 다음의 방법으로 바꿀 수 있다. PNG 파일 외에 다른 타입의 이미지를 사용하려면 wx.BITMAP_TYPE_PNG를 해당하는 상수로 바꿔주면 된다. (예를 들어 아이콘 파일(.ICO)의 경우는 wx.BITMAP_TYPE_ICO)
    class MainWindow(wx.Frame):
        def __init__(self, parent, id, title):
    
            ...
    
            ICON_PATH = 'icon_image.png'
            self.SetIcon(wx.Icon(ICON_PATH, wx.BITMAP_TYPE_PNG))
    
    
  2. 태스크바의 아이콘까지 바꾸려면 아래의 작업을 추가로 해주어야 한다.
    import ctypes   # for taskbar icon
    
    ...
    
    class MainWindow(wx.Frame):
        def __init__(self, parent, id, title):
    
            ...
    
            my_app_id = r'mycompany.myproduct.subproduct.version'   # 임의의 스트링
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(my_app_id)
    
    


- OS : Windows 10
- Python 버전 : 3.6.5
- wxPython 버전 : 4.1.1
,

자바스크립트 탭 사이즈를 2 <-> 4로(스페이스 문자 사용) 토글시키는 함수 정의입니다.

(defun jstoggle ()
  "Toggle setting javascript tab widths between 2 and 4"
  (interactive)
  (if (= js-indent-level 4)
      (setq js-indent-level 2)
    (setq js-indent-level 4)
    )
  (message (number-to-string js-indent-level))
  (redraw-display))
  


보너스


자바스크립트 탭 사이즈와 Prettier-js의 탭 사이즈를 동시에 2 <-> 4로 토글시키는 함수 정의

(defun jstoggle ()
  "Toggle setting javascript tab widths between 2 and 4"
  (interactive)
  (if (= js-indent-level 4)
      (progn
        (setq js-indent-level 2)
        (setq prettier-js-args '(
                                 "--use-tabs" "false"
                                 "--tab-width" "2"
                                 )))
    (progn
      (setq js-indent-level 4)
      (setq prettier-js-args '(
                               "--use-tabs" "false"
                               "--tab-width" "4"
                               )))
    )
  (message (number-to-string js-indent-level))
  (redraw-display))
  
<테스트 환경> 
- OS : Windows 10 
- Emacs 버전 : Emacs 24.5.1 윈도우
,

자바스크립트 모드에서 기본적으로 들여쓰기시 탭 문자를 사용하도록 되어있습니다. 이것을 스페이스를 사용하도록 설정하는 방법입니다.

 

.emacs 파일에 아래와 같이 정의합니다.

(add-hook 'js-mode-hook
          (function (lambda ()
                      (setq indent-tabs-mode nil
                            ))))

(setq-default js-indent-level 4) ; 사용할 스페이스 갯수

혹은

(dolist (hook '(js2-mode-hook js-mode-hook json-mode-hook))
  (add-hook hook (function (lambda ()
                             (setq indent-tabs-mode nil
                                   )))))

(setq-default js-indent-level 4) ; 사용할 스페이스 갯수

 

<테스트 환경> 
OS : Windows 10
Emacs 버전 : Emacs 24.5 윈도우
,

문제점

 

웹 프론트엔드 빌드 중, 다음과 같은 에러가 날 때가 있다.

Module build failed: Error: Node Sass does not yet support your current environment: Windows 64-bit with Unsupported runtime (83)

 

원인 및 대책

 

yarn install 시에 현재 설치된 nodejs 버전에 맞게 node-sass를 설치하게 되는데, 그 이후 nodejs의 버전이 바뀌었을 경우에 발생함.

 

보통은 npm rebuild node-sass를 하면 해결됨. 그러나 해당 버전의 node-sass가 현재 버전의 nodejs를 지원하지 않아 에러가 발생하는 경우가 있는데, 이 경우 node-sass를 현재 버전의 nodejs를 지원하는 버전으로 높이거나 nodejs의 버전을 낮춰야 됨.

,

생성 커맨드

yarn create react-app my-app


성공 메세지


이후 사용법에 대한 가이드가 담겨져 있다.

Success! Created my-app at D:\test\my-app
Inside that directory, you can run several commands:

  yarn start
    Starts the development server.

  yarn build
    Bundles the app into static files for production.

  yarn test
    Starts the test runner.

  yarn eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd my-app
  yarn start

Happy hacking!

<테스트 환경>
- OS : Windows 10
- yarn create v1.17.3
- create-react-app@4.0.1
,

>> [Python] 키보드 후킹 - 2. 간편화 코드


콜백 함수에 getFPTR을 호출하여 함수 포인터 얻는 것을 installHookProc 함수 안에서 하도록 처음 시도하였을 때 에러가 발생하였다.
        pointer = KeyRogue.getFPTR(KeyRogue.hookProcInternal)
         
        KeyRogue.hooked = KeyRogue.user32.SetWindowsHookExA(
            KeyRogue.WH_KEYBOARD_LL,
            pointer,
            KeyRogue.kernel32.GetModuleHandleW(None),
            0
        )

위 코드와 같이 getFPTR을 호출한 결과를 지역 변수로 받아서 SetWindowsHookExA 함수의 파라미터로 넘겼는데, static 하지않은 변수를 파라미터로 넘긴 것이 문제가 된 것이었다. 최종 코드와 같이 클래스 변수를 사용하여 파라미터를 넘겼을 때는 문제가 발생하지 않았다.

,

윈도우 환경에서 Python을 사용하여 키보드 후킹을 하는 방법 관련하여 좀더 쓰기 편하게 코드 정리를 하였다. 특수키를 확인하는 방법도 추가되었다.



소스


KeyRogue.py
# -*- coding: utf-8 -*-
#!/usr/bin/python

import sys
from ctypes import *
from ctypes.wintypes import MSG
from ctypes.wintypes import DWORD

class KeyRogue:
    user32 = windll.user32
    kernel32 = windll.kernel32

    WH_KEYBOARD_LL = 13
    WM_KEYDOWN = 0x0100
    WM_SYSKEYDOWN = 0x0104
    
    VK_CONTROL = 0x11
    VK_MENU = 0x12
    VK_SHIFT = 0x10
    
    hooked = None
    hookProc = None
    pointer = None
    
    @staticmethod
    def installHookProc(hookProc):
        if KeyRogue.hooked is not None:
            print("already installed. uninstalling..")
            KeyRogue.uninstallHookProc()
        KeyRogue.hookProc = hookProc
        KeyRogue.pointer = KeyRogue.getFPTR(KeyRogue.hookProcInternal)
        
        KeyRogue.hooked = KeyRogue.user32.SetWindowsHookExA(
            KeyRogue.WH_KEYBOARD_LL,
            KeyRogue.pointer,
            KeyRogue.kernel32.GetModuleHandleW(None),
            0
        )
        if not KeyRogue.hooked:
            return False
        return True

    @staticmethod
    def uninstallHookProc():
        if KeyRogue.hooked is None:
            return
        KeyRogue.user32.UnhookWindowsHookEx(KeyRogue.hooked)
        KeyRogue.hooked = None

    @staticmethod
    def bytes(integer):
        return divmod(integer, 0x10000)
            
    @staticmethod
    def getFPTR(fn):
        CMPFUNC = CFUNCTYPE(c_int, c_int, c_int, POINTER(c_void_p))
        return CMPFUNC(fn)

    @staticmethod
    def startKeyLog():
        msg = MSG()
        KeyRogue.user32.GetMessageA(byref(msg), 0, 0, 0)

    @staticmethod
    def getKeyCode(lParam):
        high, low = KeyRogue.bytes(lParam[0])
        return low

    @staticmethod
    def getKeyInfo(lParam):
        keyCode = KeyRogue.getKeyCode(lParam)
        return (keyCode, chr(keyCode))

    @staticmethod
    def checkModifier(lParam, vKey):
        if KeyRogue.user32.GetAsyncKeyState(vKey) & 0x8000:
            return True
        return False

    @staticmethod
    def hookProcInternal(nCode, wParam, lParam):
        if wParam != KeyRogue.WM_KEYDOWN and wParam != KeyRogue.WM_SYSKEYDOWN:
            return KeyRogue.user32.CallNextHookEx(KeyRogue.hooked, nCode, wParam, lParam)
        if KeyRogue.hookProc:
            KeyRogue.hookProc(nCode, wParam, lParam)
        return KeyRogue.user32.CallNextHookEx(KeyRogue.hooked, nCode, wParam, lParam)

    @staticmethod
    def start(hookProc):
        if KeyRogue.installHookProc(hookProc):
            KeyRogue.startKeyLog()
        else:
            print("install failed")

    @staticmethod
    def stop():
        KeyRogue.uninstallHookProc()

client.py
from KeyRogue import *

def hookProc(nCode, wParam, lParam):
    key = KeyRogue.getKeyInfo(lParam)
    print("key code = %d" % key[0])
    print("key char = %c" % key[1])
    if KeyRogue.checkModifier(lParam, KeyRogue.VK_CONTROL):
        print("CTRL pressed")
        if (key[1] == 'Q'):
            print("exit key pressed, call stop()")
            KeyRogue.stop()
            sys.exit(-1)
    if KeyRogue.checkModifier(lParam, KeyRogue.VK_MENU):
        print("ALT pressed")
    if KeyRogue.checkModifier(lParam, KeyRogue.VK_SHIFT):
        print("SHIFT pressed")

KeyRogue.start(hookProc)

  • Hook 설치 성공시 .start() 함수는 리턴되지 않는다.(심지어 stop() 함수를 사용하여 언인스톨되었을 경우에도) 따라서 GUI가 있는 프로그램에 키보드 후킹 기능을 추가하려면 별도의 쓰레드에서 실행되도록 처리가 필요하다.


참고사항

<테스트 환경>
- OS : Windows 10
- Python 버전 : 3.6.5(32/64bit), 3.7.5(32bit)


,