HW 매크로 키보드 - HW maekeulo kibodeu

* 경고 *

해당 포스팅은 교육적인 목적입니다. 절대 악용, 남용 하면 아니되옵니다.

안녕하세요 라이프온룸 입니다. !!ㅎㅎ 

오늘은 아두이노 Pro Micro로 들어온 시리얼 데이터로 매크로 Keyboard, Mouse 를 만들어 보겠습니다.

준비물은 간단합니다. Arduino Pro Micro 만 있으면 되요 ㅎㅎ

아두이노 Pro Micro는 Arduino Leonardo 호환 보드로 Atmega32u4 칩에 프로그램이 가능합니다. 그래서 시리얼 HID 모두 가능하지요 ! 다만 5V 버전과 3.3V 이 존재합니다. 5V 버전의 경우 16MHz로 동작하는데 아래 보면 오실레이터 부분에 5V인 경우에는 16 이라는 숫자가 보일 거고 3.3V 일 경우에는 8 이라는 숫자가 보일 겁니다. 

HW 매크로 키보드 - HW maekeulo kibodeu
Arduino Pro Micro 5V and 3.3V version

주의할 점은 아두이노 IDE에서 보드를 잘 못 선택하고 스케치를 업로드 하면 벽돌이 될 수 있습니다…. 저의 경우 3.3V 의 부트로더가 Lily Pad Arduino Usb 였는데 Arduino/Genuino Micro 로 스케치 업로드 했다가 바로 벽돌 됐습니다. ㅜㅜ 복구하는 방법은 아래 URL을 참고 하세요. 간단히 설명드리자면 RST 와 GND를 두번 쇼트 시키고 8초 안에 빈 스케치를 주입하시면 됩니다.

https://learn.sparkfun.com/tutorials/pro-micro–fio-v3-hookup-guide/troubleshooting-and-faq

여튼 그래서 무슨 보드를 쓰는지 잘 숙지하고 있어야 하고 스케치 업로드 할 때 주의하셔야 합니다.  Pro Micro는 잘 못 하면 벽돌이 잘 된다고 하네요 ㅜㅜ 

저의 경우 5V 버전으로 작업 했는데요 아두이노를 연결하면 아래처럼 Leonardo 라고 인식합니다. 이렇게 인식이 되면 일단 정상 동작 하는 겁니다. 

HW 매크로 키보드 - HW maekeulo kibodeu

1. 아두이노 코드 

자 그럼 이제 먼저 아두이노 코드를 짜볼게요 

/*
/mMo/12,12|
/mlC/p|
/mlC/r|
/mKey/hello|
*/

#include "Mouse.h"
#include "Keyboard.h"
#include "HID.h"

int x,y = -1;

void setup() {
  Serial.begin(9600);

  // Sends a clean report to the host. This is important on any Arduino type.
  Keyboard.begin();
  Mouse.begin();
}

void loop() {
  if (Serial.available())
  {
    String mouseString = Serial.readStringUntil('|');
    mouseString.trim();
    if (mouseString.charAt(0) == '/')
    {
      // send like this - "/mMo/5,5"
      if (mouseString.substring(1, 4).equals(F("mMo")) == true)
      {
            //-------------------------------   
            int slashIdx = mouseString.indexOf('/', 1);
            if (slashIdx > 0)
            {
              int commaIdx = mouseString.indexOf(',', slashIdx + 1);
              if (commaIdx > 0)
              {
                x = mouseString.substring(slashIdx + 1, commaIdx).toInt();
                y = mouseString.substring(commaIdx + 1).toInt();
                
              }
              else
              {
                x = mouseString.substring(slashIdx + 1).toInt();
                y = 0;
              }
              Mouse.move(x, y, 0);
              //Serial.print(x);Serial.print(':');Serial.println(y);
            }
            //-------------------------------         
            Serial.println(F("move"));
      }
      else if (mouseString.substring(1, 4).equals(F("mlC")) == true)
      {
            //-------------------------------   
            int slashIdx = mouseString.indexOf('/', 1);
            if (slashIdx > 0)
            {
              char action = mouseString.substring(slashIdx + 1)[0];
              if ((action == 'p') && (!Mouse.isPressed(MOUSE_LEFT)))
              {
                Mouse.press(MOUSE_LEFT);
                Serial.println(F("Pre"));
              }
              else if ((action == 'r') && (Mouse.isPressed(MOUSE_LEFT)))
              {
                Mouse.release(MOUSE_LEFT);
                Serial.println(F("Rls"));
              }
            }
            else
            {
              Mouse.press();
              delay(400);
              Mouse.release();
              Serial.println(F("Click"));
            }
            //-------------------------------         
      }
      else if (mouseString.substring(1, 4).equals(F("mrC")) == true)
      {
            //-------------------------------   
            int slashIdx = mouseString.indexOf('/', 1);
            if (slashIdx > 0)
            {
              char action = mouseString.substring(slashIdx + 1)[0];
              if ((action == 'p') && (!Mouse.isPressed(MOUSE_RIGHT)))
              {
                Mouse.press(MOUSE_RIGHT);
                Serial.println(F("Pre"));
              }
              else if ((action == 'r') && (Mouse.isPressed(MOUSE_RIGHT)))
              {
                Mouse.release(MOUSE_RIGHT);
                Serial.println(F("Rls"));
              }
            }
            else
            {
              Mouse.press(MOUSE_RIGHT);
              delay(400);
              Mouse.release(MOUSE_RIGHT);
              Serial.println(F("Click"));
            }
      }
      else if (mouseString.substring(1, 5).equals(F("mKey")) == true)
      {
            //-------------------------------   
            int slashIdx = mouseString.indexOf('/', 1);
            if (slashIdx > 0)
            {
              
              Keyboard.print(mouseString.substring(slashIdx + 1));
            }
            //-------------------------------         
            
            Serial.println(F("Key"));
      }      
      
    }
  }
}

아래 명령예시 입니다. 마우스 이동, 좌클릭, 우클릭, Keyboard 타자를 지원 합니다. 

  • /mMo/12,12| – 마우스를 현위치에서 12, 12, 만큼 움직임
  • /mlC/p| – 현재 좌표 마우스 누르기
  • /mlC/r| – 현재 좌표 마우스 때기
  • /mKey/hello| – hello 그대로 Keyboard 입력 

요 상태에서 아래 설정으로 스케치를 업로드를 합니다. (5V 16MHz 사용 중)

  • 보드 : Arduino/Genuino Micro
  • 포트 :COM21 (Arduino/Genuino Micro)

HW 매크로 키보드 - HW maekeulo kibodeu

업로드 후 시리얼 모니터를 열고 위 예시를 시험해보세요 !! 명령이 성공하면 명령 종류에 따라서 String을 Serial로 Return 해 줍니다. ㅎ

HW 매크로 키보드 - HW maekeulo kibodeu

2. Python Code 

그럼 윈도우에서 Python Code로 키보드 ‘F4’를 누르면 3초 뒤 “Hello”를 출력하는 예제를 해보겠습니다. 우선 아래 라이브러리를 설치하고 갈게요 

pip install pyserial
pip install keyboard
pip install mouse

설치를 하셨으면 serialFunc.py 라는 파일을 만들고 아래 코드를 입력해 줍니다. 

import serial
import time


# /mMo/12,12|
# /mlC|
# /mlC/p|
# /mlC/r|

# /mrC|
# /mrC/p|
# /mrC/r|

# /mKey/keyinput|


class ExternalHID:
    ser = 0
    def __init__(self, comport):
        try:
            self.ser = serial.Serial(comport, 9600, timeout=1)

        except Exception as e:
            print(str(e))

    def disconnectSerial(self):
        self.ser.close()

    def checkSerial(self):
        if self.ser == 0:
            print('Serial Not available')
            return False
        else:
            return True

    def mouseMove(self, x, y):
        if not self.checkSerial():
            return False

        moveCommand = '/mMo/%d,%d|' % (x, y)
        self.ser.write(str.encode(moveCommand))
        rsp = self.ser.readline()
        if rsp.strip() == b'move':
            return True
        else:
            return False

    def mouseClick(self, button):
        if not self.checkSerial():
            return False

        if button == 'left':
            moveCommand = '/m%sC|' % ('l')
        elif button == 'right':
            moveCommand = '/m%sC|' % ('r')
        else:
            return False
        self.ser.write(str.encode(moveCommand))
        rsp = self.ser.readline()
        if rsp.strip() == b'Click':
            print('HID Click Success')
            return True
        else:
            return False

    def keyboardInput(self, keyinput):
        if not self.checkSerial():
            return False

        moveCommand = '/mKey/%s|' % keyinput
        self.ser.write(str.encode(moveCommand))
        rsp = self.ser.readline()
        if rsp.strip() == b'Key':
            return True
        else:
            return False

    def mousePress(self, button):
        if not self.checkSerial():
            return False
        print(str(button))
        if button == 'left':
            moveCommand = '/m%sC/p|' % ('l')
        elif button == 'right':
            moveCommand = '/m%sC/p|' % ('r')
        else:
            return False

        self.ser.write(str.encode(moveCommand))
        rsp = self.ser.readline()
        if rsp.strip() == b'Pre':
            return True
        else:
            return False

    def mouseRelease(self, button):
        if not self.checkSerial():
            return False
        print(str(button))
        if button == 'left':
            moveCommand = '/m%sC/r|' % ('l')
        elif button == 'right':
            moveCommand = '/m%sC/r|' % ('r')
        else:
            return False

        self.ser.write(str.encode(moveCommand))
        rsp = self.ser.readline()
        if rsp.strip() == b'Rls':
            return True
        else:
            return False

if __name__ == "__main__":

    import keyboard as key
    import time

    ser = ExternalHID('COM21')


    def printHello():
        state = False
        returnList = []
        while True:
            val = key.is_pressed('F4')
            if state != val:
                if val == True:
                    time.sleep(3)
                    ser.keyboardInput('Hello')
                state = val


    while True:
        time.sleep(0.001)
        printHello()

이 코드는 아두이노에서 정의한 Keyboard, Mouse 제어 스트링을 Serial 통신으로 보내주는 역할을 합니다. 다양한 함수를 만들어 놨으니 한번 사용해 보세요 !

코드를 입력 했으면 Pycharm 혹은 Cmd 에서 실행시킨 뒤 메모장에 마우스를 위치시키고  ‘F4’ 를 눌러보세요 ! 그럼 약 3초 뒤에 Hello 라는 단어가 메모장에 써질 겁니다. 

이쯤에서 로아 낚시 매크로 시리즈를 보고 오신 분이 계신다면 이게 뭔 뻘짓이지 ? 그냥 ser.keyboardInput(‘Hello’) 대신 key.write(‘Hello’) 쓰면 되는거 아닌가? 라는 아주 합리적인 의문을 가지실 수 있습니다. 

하지만 후자는 S/W 적인 방법입니다. 게임에 적용 할 때면 안 먹히는 경우가 꽤 있습니다. 특히 게임 상에서 마우스 이벤트로 특정 버튼을 누르려 할 때 안되는 경우가 종종 있습니다.(물론 이건 제 지식이 짧아서 일 수 있습니다… ㅜㅜ) 

이 포스트의 하드웨어 메크로는 가능데스죠 ㅋㅋ 악용은 금지 입니다. ..

오늘의 포스팅은 여기까지 입니다. 모두들 좋은 밤 되세요 ㅎㅎㅎ

HW 매크로 키보드 - HW maekeulo kibodeu