[완전판] 지연 제로·오작동 없음. Android용 '범용 물리 트리거 버튼'을 처음부터 직접 만드는 전문가용 가이드

[완전판] 지연 제로·오작동 없음. Android용 '범용 물리 트리거 버튼'을 처음부터 직접 만드는 전문가용 가이드

|도구

이 기사에서는 우천 시의 오작동이나 절전 모드 복귀 지연으로 고민하는 음식 배달 라이더, 혹은 Android 기기의 조작을 극한으로 효율화하고 싶은 기크(Geek)를 위해, 현장의 집념이 만들어낸 '범용 물리 트리거 버튼'의 완전한 제작 절차를 설명합니다.

제작 당시 X 게시물 1 제작 당시 X 게시물 2 제작 당시 X 게시물 3 제작 당시 X 게시물 4 제작 당시 X 게시물 5

1. 콘셉트: 왜 '자작'에 이르게 되었는가

본 가이드는 실전적인 하드웨어 및 소프트웨어 구축 절차에 특화되어 있지만, 이 '궁극의 물리 버튼'이 탄생하기까지의 우천 시 도로 위에서의 거듭된 실패와 시행착오 과정은 아래의 note 기사에 기록해 두었습니다. 왜 기존 제품으로는 안 되었는지, 개발에 이르기까지의 끈질긴 이야기에 관심이 있으신 분은 함께 읽어보시기 바랍니다.

시중에서 판매되는 저렴한 Bluetooth 버튼이나 미디어 컨트롤러에는 업무 수준의 가혹한 환경(우천, 연속 가동, 순간적인 판단을 요하는 조작)에서 치명적인 약점이 존재합니다.

  • 절전 모드 복귀 지연: 절전 기능으로 인해, 막상 누르려고 할 때 첫 번째 클릭이 인식되지 않습니다.

  • 레이아웃 간섭: 특정 앱(Uber Eats 등)에서 시중 제품의 입력 신호가 예상치 못한 동작을 일으킵니다.

  • 채터링과 침수: 물리적인 밀폐성이 낮아, 비 오는 날의 높은 습도나 물방울로 인해 오작동(채터링)이 빈번하게 발생합니다.

이러한 과제를 근본적으로 해결하기 위해, 본 프로젝트에서는 Arduino 호환 마이크로컨트롤러를 사용하여 C++로 펌웨어를 직접 제작하고, OS에 '최적의 거동을 하는 미디어 리모컨'으로 인식시키는 접근 방식을 취합니다. 이를 통해 끊김 없고 지연 없는 확실한 조작 환경을 구축합니다.

이러한 과제를 근본적으로 해결하기 위해, 본 프로젝트에서는 Arduino 호환 마이크로컨트롤러를 사용하여 C++로 펌웨어를 직접 제작하고, OS에 '최적의 거동을 하는 미디어 리모컨'으로 인식시키는 접근 방식을 취합니다. 이를 통해 끊김 없고 지연 없는 확실한 조작 환경을 구축합니다.

2. 필요한 부품 (BOM: 부품 명세서)

아키하바라의 전자부품 전문점이나 Amazon에서 입수 가능한, 실전 투입을 통해 검증된 부품 리스트입니다. 안정성을 중시하여 선정했습니다.

부품명모델명 / 상세최저 참고 가격(세금 포함)입수처선정 이유
마이크로컨트롤러 기판Seeed Studio XIAO nRF528401,800엔센고쿠덴쇼초소형, Bluetooth 5.0 대응, 배터리 충방전 회로 내장. 이 용도에 최적의 기판.
Amazon
배터리Li-po 충전지 3.7v 110mAh880엔센고쿠덴쇼XIAO 사이즈에 들어가며 구동 시간을 확보할 수 있는 용량.
Amazon
키 스위치Kailh Choc Brown (갈축)60엔/개유샤코보로우 프로파일로 확실한 클릭감이 있는 갈축 채용. 오입력 방지.
유샤코보
키캡chocfox CFX 1U keycap550엔/10개유샤코보Choc 스위치 전용. 평평하고 누르기 쉬운 형태.
유샤코보
배선재불필요한 USB 케이블 등의 심선0엔폐자재 활용가늘고 유연하여 극소 공간에서의 배선 처리에 용이함.
케이스3D 프린터 출력물 등-자작100엔 숍 케이스 등으로 자작해도 좋습니다. 저는 3D 프린터로 제작했습니다.

3. 필요한 도구 및 소모품

정밀한 작업이므로 적절한 도구 준비가 성공의 열쇠입니다.

  • 인두기: 온도 조절 기능이 있는 것을 권장합니다. 인두 팁은 너무 가늘지 않은 것이 열전달이 좋습니다.

  • 납: goot 고밀도 실장용 SD-81 (0.6mm)\ Amazon - 고정 기구: 서포트 스탠드 플렉시블 암 타입\ Amazon - 점착 겔 양면테이프: 캔두 제품 (두께 1.8mm). ※ 이 1.8mm라는 두께에 맞춰 3D 프린트 케이스 치수를 잡았으므로, 오리지널 케이스를 제작하시는 분은 필요 없거나 다른 방법으로 기판과 배터리를 고정해 주세요.

4. 하드웨어 구현: 확실한 조립의 비결

이 공정에서의 실수는 동작 불량으로 직결됩니다. 특히 납땜의 기본과 배터리 취급에는 각별히 주의해 주십시오.

4-1. 배선 준비

사용하지 않는 충전 케이블(USB 케이블 등)의 외피를 벗기고, 안에 들어 있는 극세 도선(심선)을 꺼냅니다. 이를 필요한 길이로 자르고 양 끝의 피복을 2~3mm 정도 벗긴 뒤, 미리 소량의 납을 묻혀둡니다(예비 납땜).

4-2. 스위치와 기판 납땜

XIAO nRF52840과 키 스위치를 연결합니다.

  1. 연결처:

    • 버튼 1 (주 버튼): 키 스위치의 한쪽 핀을 XIAO의 D0 핀으로, 다른 쪽을 GND 핀으로.

    • 버튼 2 (부 버튼): 키 스위치의 한쪽 핀을 XIAO의 D1 핀으로, 다른 쪽을 GND 핀으로.

      (※GND는 공통이므로, 스위치 측에서 데이지 체인처럼 연결해도 무방합니다)

  2. 납땜 요령 (예열):

    납이 잘 붙지 않는 원인의 대부분은 '열 부족'입니다. 인두 팁을 기판의 패드(금속 부분)와 배선 양쪽에 동시에 대고, 1~2초 정도 예열한 뒤 납을 공급합니다. 납이 싹 후지산 모양으로 퍼지면 성공입니다. 둥글게 뭉쳐 있다면 열 부족이거나 플럭스 부족입니다.

4-3. 배터리 연결 (경고: 쇼트 주의)

배터리는 기판 뒷면에 있는 배터리 연결용 패드(BAT+BAT-)에 직접 납땜합니다.

  • 쇼트 엄금: 리튬 폴리머 배터리 배선을 자를 때, 절대로 플러스 선과 마이너스 선을 니퍼로 동시에 자르지 마십시오. 날을 통해 쇼트가 발생하여 화재의 위험이 있습니다. 반드시 하나씩 자르고, 한쪽 작업이 끝날 때까지 다른 쪽은 테이프 등으로 절연해 둡니다.

  • 연결 절차: 안전을 위해 먼저 **마이너스(검은 선)를 기판의 BAT-**에 납땜하고, 그 후에 **플러스(빨간 선)를 BAT+**에 납땜합니다.

납땜01

납땜02

납땜03

4-4. 조립 및 고정 (단선 방지 대책)

극소 사이즈의 전자 공작에서 가장 많은 트러블은 '배선 뿌리 부분의 단선'입니다.

납땜이 끝나면 기판이나 배선을 함부로 움직이지 마십시오.

단선을 방지하기 위해 납땜 완료 후 즉시, 준비한 1.8mm 두께의 겔 타입 양면테이프를 사용하여 기판, 배터리, 키 스위치를 3D 프린트 케이스 내에 일체화시켜 단단히 고정합니다. 이 테이프의 두께와 탄력성이 충격 흡수와 배치 최적화를 겸합니다.

※ 1.8mm 두께의 겔 타입 양면테이프는 자작 케이스에 수납하기 위한 것이므로, 오리지널 환경에서 구축할 경우 다른 방법으로 제작해도 괜찮습니다. ※ 다음 납땜 준비 등을 위해 움직이다 보면 배선이 단선될 수 있으므로, 즉시 고정하는 것을 추천합니다.

5. 소프트웨어: 펌웨어 기록

마이크로컨트롤러를 '고성능 Bluetooth 버튼'으로 기능하게 하기 위한 두뇌를 불어넣습니다.

5-1. Arduino IDE 설정 및 업로드

PC에 https://www.google.com/search?q=https://www.arduino.cc/en/software 에서 Arduino IDE를 설치하고, 필요한 보드 패키지(Seeed nRF52 mbed-enabled Boards)를 추가합니다.

288

 설치한 IDE를 실행한 후 PC와 Seeed Studio XIAO nRF52840을 USB 케이블로 연결합니다.

  1. 보드 검색: 메뉴에서 Select Other Board and Port를 열고, **Seeed Studio XIAO nRF52840**을 검색하여 선택합니다.

    (※주의: Seeed Studio XIAO 시리즈에는 "무인(SAMD21)", "ESP32C3" 등 비슷한 이름의 다른 기판이 존재합니다. 반드시 "nRF52840"을 지정하십시오. 잘못 선택하면 업로드할 수 없습니다)

    보드 선택: 검색하면 선택란에 Seeed Studio XIAO nRF52840이 나타나므로 선택합니다. Plus나 Seeed 등의 버전 차이가 있으니 주의하십시오.

  2. 포트 선택:

    Tools -> Port에서, **Seeed Studio XIAO nRF52840**이 연결된 포트(COM 포트 등)를 선택합니다. ※mac의 경우 tools는 윈도우 내에 있지 않고 작업 표시줄에 있습니다.

    업로드: 보드와 포트 준비가 완료되면 중앙의 텍스트 입력란에 아래 내용을 붙여넣으십시오.

#include <bluefruit.h>

// --- カスタマイズ設定 ---
#define DEVICE_NAME "D-Remote" // 11文字以内の好きな名前に変更してください
#define BTN1_PIN 0             // D0 (Vol Up / 長押し機能)
#define BTN2_PIN 1             // D1 (Vol Down)
// ----------------------

BLEDis bledis;
BLEBas blebas; // バッテリーサービス
BLEHidGeneric blehid(1, 0, 0);

static uint16_t inputReportLen[] = { 1 };
#define REPORT_ID_CONSUMER_CONTROL 1

// メディアリモコン設計図
uint8_t const hidReportDescriptor[] = {
  0x05, 0x0C,                   // Usage Page (Consumer Devices)
  0x09, 0x01,                   // Usage (Consumer Control)
  0xA1, 0x01,                   // Collection (Application)
  0x85, REPORT_ID_CONSUMER_CONTROL, // Report ID (1)
  0x15, 0x00,                   // Logical Minimum (0)
  0x25, 0x01,                   // Logical Maximum (1)
  0x75, 0x01,                   // Report Size (1 bit)
  0x95, 0x03,                   // Report Count (3)
  0x09, 0xE9,                   // Usage 1: Volume Increment (Bit 0)
  0x09, 0xEA,                   // Usage 2: Volume Decrement (Bit 1)
  0x09, 0xCD,                   // Usage 3: Play/Pause       (Bit 2)
  0x81, 0x02,                   // Input (Data, Variable, Absolute)
  0x75, 0x01,                   // Report Size (1 bit)
  0x95, 0x05,                   // Report Count (5) - パディング
  0x81, 0x03,                   // Input (Constant, Variable, Absolute)
  0xC0                          // End Collection
};

// 実際のバッテリー残量をパーセントで計算する関数
uint8_t getBatteryPercentage() {
  // バッテリー電圧読み取り用のスイッチ(VBAT_ENABLE)をONにする
  pinMode(VBAT_ENABLE, OUTPUT);
  digitalWrite(VBAT_ENABLE, LOW);
  delay(5); // 電圧が安定するまで待機

  analogReference(AR_INTERNAL); // 基準電圧3.6V
  analogReadResolution(12);     // 12bit解像度
  int raw = analogRead(PIN_VBAT); // 電圧読み取り

  // 読み取りが終わったらスイッチをOFFにして放電を防ぐ
  digitalWrite(VBAT_ENABLE, HIGH);
  pinMode(VBAT_ENABLE, INPUT);

  // XIAO nRF52840の正確な抵抗比(1MΩと510kΩ)に基づいた電圧計算(mV)
  // 計算式: 読み取り値 * (基準電圧/分解能) * (分圧比補正)
  float mv = raw * (3600.0 / 4096.0) * (1510.0 / 510.0);

  // リチウムイオン電池の特性に合わせる (4200mV=100%, 3300mV=0%)
  if (mv >= 4200.0) return 100;
  if (mv <= 3300.0) return 0;
  return (uint8_t)((mv - 3300.0) / (4200.0 - 3300.0) * 100.0);
}

void setup() {
  pinMode(BTN1_PIN, INPUT_PULLUP);
  pinMode(BTN2_PIN, INPUT_PULLUP);

  Bluefruit.begin();
  Bluefruit.setTxPower(8);
  Bluefruit.setName(DEVICE_NAME);
  Bluefruit.setAppearance(BLE_APPEARANCE_HID_GAMEPAD);

  bledis.setManufacturer("HI-JA");
  bledis.setModel("Delivery Controller");
  bledis.begin();

  blebas.begin();
  blebas.write(getBatteryPercentage()); // 起動時に実際のバッテリー残量を送信

  blehid.setReportLen(inputReportLen);
  blehid.setReportMap(hidReportDescriptor, sizeof(hidReportDescriptor));
  blehid.begin();

  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();
  Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_HID_GAMEPAD);
  Bluefruit.Advertising.addService(blehid);
  Bluefruit.Advertising.addService(blebas);
  Bluefruit.Advertising.addName();
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 244);
  Bluefruit.Advertising.setFastTimeout(30);
  Bluefruit.Advertising.start(0);
}

void loop() {
  bool raw1 = (digitalRead(BTN1_PIN) == LOW);
  bool raw2 = (digitalRead(BTN2_PIN) == LOW);

  static int state = 0;
  static unsigned long pressTime = 0;
  static uint8_t lastReport = 0;
  uint8_t newReport = 0;
  unsigned long now = millis();

  // --- 同時押し判定ステートマシン ---
  if (state == 0) {
    if (raw1 || raw2) {
      state = 1;
      pressTime = now;
    }
  } else if (state == 1) {
    if (!raw1 && !raw2) {
      state = 0;
    } else if (now - pressTime > 40) {
      if (raw1 && raw2) state = 4;
      else if (raw1) state = 2;
      else if (raw2) state = 3;
    }
  } else {
    if (state == 2 && !raw1) state = 0;
    if (state == 3 && !raw2) state = 0;
    if (state == 4 && (!raw1 && !raw2)) state = 0;
  }

if (state == 2) newReport = 0x01;
  else if (state == 3) newReport = 0x02;
  else if (state == 4) newReport = 0x04;
  else newReport = 0x00;

  if (newReport != lastReport) {
    blehid.inputReport(0, &newReport, 1);
    lastReport = newReport;
  }

  // --- 길게 누르기 기능 ---
  static unsigned long d0StartTime = 0;
  static bool d0Long10s = false;
  static bool d0Long15s = false;

  if (state == 2 && raw1) {
    if (d0StartTime == 0) d0StartTime = now;
    unsigned long duration = now - d0StartTime;

    if (duration >= 15000 && !d0Long15s) {
      d0Long15s = true;
      uint8_t releaseReport = 0;
      blehid.inputReport(0, &releaseReport, 1);
      delay(100);
      nrf_gpio_cfg_sense_input(g_ADigitalPinMap[BTN1_PIN], NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
      NRF_POWER->SYSTEMOFF = 1;
    }
    else if (duration >= 10000 && !d0Long10s && !d0Long15s) {
      d0Long10s = true;
      NVIC_SystemReset();
    }
  } else {
    d0StartTime = 0;
    d0Long10s = false;
    d0Long15s = false;
  }

  // 실제 배터리 잔량을 60초마다 업데이트
  static unsigned long lastBatteryTime = 0;
  if (now - lastBatteryTime > 60000) {
    lastBatteryTime = now;
    blebas.write(getBatteryPercentage());
  }
  delay(5);
}

  • 붙여넣기가 완료되면 화면 왼쪽 상단에 있는 (오른쪽 화살표 아이콘)을 눌러 쓰기를 실행해 주세요.

    # 게이지 표시가 시작되고 완료되면 그대로 분리해도 괜찮습니다. 쓰기 중에는 분리하지 마세요.

    쓰기 오류가 발생한 경우 설정이나 붙여넣기 과정에서 문제가 발생했을 가능성이 있습니다. 오류 코드를 복사한 후 Gemini나 ChatGPT에 문의해 보세요.

5-2. 문제 해결: 포트가 인식되지 않거나 멈춘 경우

개발 중에 기판이 PC에서 인식되지 않거나(포트가 표시되지 않음) 멈추는 현상이 자주 발생합니다.

  • 기본 리셋: 기판 위의 "RST" 패드를 핀셋 등으로 빠르게 2번 쇼트(더블 클릭)시키면 부트로더 모드로 진입하여 인식되는 경우가 많습니다.

  • 최후의 수단 (완전히 멈춘 경우): 리셋 조작을 해도 전혀 반응이 없는 경우, 기판이 완전히 멈춘 상태일 수 있습니다. 이 경우 리셋 버튼 등으로는 복구되지 않습니다. 배터리를 연결한 채로 방치하여 완전히 방전(배터리 소진)될 때까지 기다리는 다소 투박한 방법으로만 강제 재시작이 가능한 경우가 있습니다.

5-3. 프로그래밍된 버튼의 동작 및 사양

작성된 코드에는 40ms의 상태 머신이 구현되어 있어 채터링(오작동)을 극한으로 제거했습니다. 또한 전압 읽기 기능(VBAT_ENABLE)을 제어하여 스마트폰으로 정확한 배터리 잔량을 전송합니다.

【버튼 동작 사양】

  • 버튼 0 (D0) 짧게 누르기: 볼륨 업

  • 버튼 1 (D1) 짧게 누르기: 볼륨 다운

  • 버튼 0 + 버튼 1 동시 누르기: 재생 / 일시 정지

  • 버튼 0을 10초간 길게 누르기: Bluetooth 페어링 정보 초기화 (리셋)

  • 버튼 0을 15초간 길게 누르기: 전원 끄기

【배터리 구동 시간】

입력 조작을 하지 않는 대기 상태에서 약 20~30일간 지속됩니다. 실제 배송 업무 등에서 빈번하게 사용하는 환경에서는 조작 횟수에 따라 이보다 짧아질 수 있습니다.

정상적으로 쓰기가 완료되고 동작이 시작되면, 기판의 파란색 LED 램프가 깜빡이며 페어링 대기 상태가 됩니다.

6. Android 설정: 단순한 버튼을 최강의 트리거로

하드웨어가 완성되고 스마트폰과 Bluetooth 페어링을 마쳤다면, 다음은 Android OS 측에서 이 입력을 후킹(hooking)하여 원하는 동작으로 변환할 차례입니다. 이 설정을 하지 않으면 단순히 볼륨만 조절되는 버튼이 되어버립니다.

6-1. 사전 준비: 개발자 옵션 활성화

Android의 고급 시스템 권한을 사용하기 위해 먼저 개발자 모드를 활성화합니다.

  1. Android의 설정휴대전화 정보를 엽니다.

  2. 빌드 번호를 7번 연속으로 탭하여 개발자 모드를 활성화합니다.

  3. 설정시스템개발자 옵션을 열고 "USB 디버깅" 및 **"무선 디버깅"**을 ON으로 설정합니다.

6-2. FRep2의 도입 및 상세 매크로 작성(이미지 인식 탭)

Key Mapper의 고정 위치 탭 기능이 아닌, 「화면상에 특정 버튼(이미지)이 나타났을 때만 탭 판정을 수행하는」 고도화된 매크로를 사용하고 싶은 경우에는, Key Mapper 설정보다 먼저 FRep2 측에서 매크로를 완성해 두어야 합니다.

(※ 단순히 같은 위치를 직접 탭하게 하고 싶은 경우에는 이 단계를 건너뛰고 「6-3. Shizuku의 도입」으로 진행해 주세요)

① FRep2 설치 및 「정밀 모드」로 변경(필수)

FRep2에서 이미지 검색을 통한 하드 탭을 실행하려면, 기본값인 「심플 모드」에서 「정밀 모드」로 변경하고 화면 캡처 권한을 부여해야 합니다.

  1. Play 스토어에서 **「FRep2」**를 설치합니다.

  2. FRep2 공식 사이트의 설정 가이드( https://strai.x0.com/frep2-ja/setup )에 접속합니다.

  3. 페이지 내의 **【정밀 모드 설정・화면 캡처 권한 부여】**에서, 작업 환경(Windows용 / Mac용 / Linux용 / Android 단독용)에 맞는 **[화면 캡처 권한 부여 도구]**를 다운로드합니다.

  4. 공식 사이트의 안내에 따라 도구를 실행하고(※ 이 작업 시 「USB 디버깅」 또는 「무선 디버깅」을 이용합니다), 권한을 부여하여 서비스를 정밀 모드로 가동해 주세요.

② 사전 준비(타겟 스크린샷)

조작하고 싶은 앱(Uber Eats 등)을 열고, 탭하고 싶은 버튼이 찍힌 화면의 스크린샷을 저장해 둡니다.

(※ 보안 등으로 인해 스크린샷을 찍을 수 없는 앱에서는 이미지 인식 탭을 사용할 수 없습니다)

③ 신규 매크로 작성 및 표시 설정

  1. FRep2를 실행하고, 메인 화면에서 기록신규를 탭합니다.

  2. 오른쪽에 나열된 설정 항목 중에서 **【표시 설정】**을 열고, 어떤 앱에서나 표시를 탭하여 **창고(표시 없음)**을 선택합니다.

    (※ 바로가기를 통해 백그라운드에서 호출하므로, 조작 패널을 표시할 필요는 없습니다)

④ 이미지 인식 프로그래밍

  1. 메뉴의 【프로그램】 내에 있는 재생 내용 편집을 탭합니다.

  2. 화면상의 버튼을 누르고, 이미지 인식이미지 파일에서를 탭합니다.

    (※ 여기서 이미지를 선택할 수 없는 경우에는, 단말기의 「설정」 > 「앱」에서 FRep2를 열고 이미지 파일에 대한 접근 권한을 부여해 주세요)

  3. 사전에 준비한 스크린샷 이미지를 열고, 오른쪽 아래의 적용을 누릅니다.

⑤ 인식 타겟 영역 지정

  1. 파란색 사각형 테두리가 표시된 화면이 나타납니다. 이 파란색 테두리가 「인식할 타겟」입니다.

  2. 테두리의 중심을 탭하고 싶은 장소(예: Uber Eats의 거절 「×」 마크의 중심 등)에 맞춥니다.

  3. 중요: 인식하는 테두리는 「작을수록」 처리가 빨라집니다. 거절 버튼이라면 가로로 길쭉하게 하여 × 마크의 중심 부근을 작게 지정하면 됩니다. 지정이 완료되면 오른쪽 아래의 적용을 누릅니다.

⑥ 인식 설정 및 검색 범위 최적화(고속화의 핵심)

  1. 다음 화면의 **【인식 설정】**에서 색상 유사도를 탭하고, **검색:유사도**로 변경합니다.

  2. **인식 영역의 중앙을 탭**에 체크합니다.

  3. **검색 범위:화면 전체**를 탭하면, 4곳에 숫자를 입력할 수 있는 그림이 나옵니다.

  4. 오른쪽 위에 있는 「이미지 표시(사진을 가리키는 듯한 아이콘)」를 탭합니다.

    이미지의 수치는 액정 크기에 따라 변하므로 참고하지 말고 반드시 소지한 단말기에서 크기 조정을 수행해 주세요.

  5. 방금 전 파란색 테두리로 설정한 타겟 부분만 밝게 표시됩니다.

    Uber Eats의 거절 버튼 등, 팝업에 따라 상하 위치가 변동되는 버튼의 경우에는 그 변동되는 최대 폭에 맞춰 밝은 영역을 넓혀주세요.

    (※ 이 부분도 검색 범위가 작을수록 처리가 압도적으로 빨라집니다. 필요한 최소한으로 유지하세요)

  6. 적용을 눌러 설정합니다.

  7. **【고급 설정】**에서 스크린샷 저장이 필요 없다면 체크를 해제합니다.

  8. 화면 오른쪽 아래의 닫기를 눌러 이미지 검색 설정을 종료합니다.

  9. 편집 화면의 처음에 있는 「대기 5.0」은 불필요한 타임 로스가 되므로 삭제해도 괜찮습니다.

⑦ 설정 저장 및 확인

  1. 화면 오른쪽 위의 **「플로피 디스크 아이콘」**을 탭하여 설정을 저장합니다.

  2. 왼쪽 위의 뒤로 가기 화살표(또는 햄버거 메뉴)에서 기록을 탭하여 설정 목록으로 돌아갑니다.

  3. 상단 띠에 있는 표준 패널을 탭하고, **창고**로 변경합니다.

  4. 방금 작성한 매크로가 표시되어 있다면 FRep2 측의 준비는 완료입니다.

6-3. Shizuku의 도입 및 실행(시스템 권한 확보 및 응답 속도 향상)

일반적으로 루팅(Rooting)이 필요한 수준의 시스템 조작(ADB를 통한 화면 하드 탭 등)을 가능하게 하기 위해, Shizuku를 상주시킵니다.

(※ FRep2의 이미지 인식 탭을 이용할 경우, Shizuku 설정 없이도 일단 동작은 하지만, Shizuku 환경 하에서 응답 속도가 압도적으로 빨라지므로 도입을 강력히 권장합니다)

  1. Google Play 스토어에서 **「Shizuku」**를 설치합니다.

    (※ 주의: 일본의 Pixel 9a 등, 일부 단말기나 국가/지역에 따라 Play 스토어에 Shizuku가 표시되지 않을 수 있습니다. 그럴 경우 보안 확보를 위해 반드시 공식 GitHub 릴리스 페이지 https://github.com/RikkaApps/Shizuku/releases 에서 최신 APK 파일을 직접 다운로드하여 설치(사이드로드)해 주세요. 출처 불명의 서드파티 사이트에서의 다운로드는 권장하지 않습니다.)

  2. 통신 환경 준비(중요): Shizuku 실행에는 「무선 디버깅」을 이용하므로, Wi-Fi에 연결할 수 있는 환경, 또는 다른 스마트폰으로부터의 테더링에 연결할 수 있는 환경이 필수입니다. 이 타이밍에 Wi-Fi 또는 테더링에 연결해 주세요.

    (※ 안심하세요. 이 연결은 Shizuku를 실행하기 위한 것뿐입니다. 무사히 실행이 완료된 후에는 연결을 해제해도 상관없습니다. 테더링을 제공하는 스마트폰의 데이터를 계속해서 소비하지 않습니다.)

  3. 개발자 옵션 확인: 만약을 위해 단말기의 설정시스템개발자 옵션이 표시되어 있으며, 「무선 디버깅」이 ON으로 되어 있는지 확인해 주세요. (개발자 옵션이 표시되어 있지 않은 경우에는 앞서 언급한 대로 설정기기 정보빌드 번호를 7번 연타하여 표시합니다)

  4. Shizuku 앱을 실행하고, 「무선 디버깅으로부터 실행」 섹션에 있는 페어링을 탭합니다.

  5. 개발자 옵션의 무선 디버깅 화면으로 전환되므로, 페어링 코드로 기기 페어링을 탭합니다.

  6. 표시된 6자리 코드를 Shizuku 알림(또는 팝업)에 입력합니다.

  7. Shizuku 앱으로 돌아와 실행을 탭합니다. 상단에 「Shizuku가 실행 중입니다」라고 표시되면 성공입니다. 실행 확인 후에는 Wi-Fi나 테더링 연결을 해제해도 괜찮습니다. (※ 단말기를 재부팅한 경우에는 다시 이 실행 절차가 필요할 수 있습니다)

6-4. Key Mapper 설정 (입력 리매핑 및 액션 할당)

물리 버튼의 입력을 감지하여 임의의 액션으로 변환하는 핵심 설정입니다. 마지막으로 여기서 모든 동작을 연동시킵니다.

※ 이 설정을 수행하려면 실제로 키를 눌러 신호를 기록해야 하므로, 사전에 자작한 Bluetooth 버튼을 스마트폰에 연결(페어링)해 두십시오.

  1. Play 스토어에서 **「Key Mapper」**를 설치하고 실행합니다.

  2. 최초 실행 시, 화면의 지시에 따라 접근성 권한을 부여하고, 추가로 Shizuku 권한도 허용합니다.

  3. 화면 하단의 버튼(또는 + new key map)을 탭하면, 화면 상단에 TriggerActions가 나열된 규칙 생성 화면이 열립니다.

  4. 트리거(실행 계기) 기록:

    1. 화면 하단에 있는 빨간색 tap to record trigger (또는 Record Trigger)를 탭합니다.

    2. 화면 표시가 「Press your keys」로 바뀐 동안, 자작한 물리 버튼의 「버튼 1(예: 볼륨 업)」을 누릅니다.

    3. 화면에 누른 Bluetooth 기기 이름과 「Volume Up」 등의 신호가 기록되면 성공입니다.

      (※ 포인트: 전송되는 신호는 「볼륨 업」이지만, Key Mapper를 통하면 실제 단말기의 볼륨을 높이지 않고, 당신이 지정한 다른 액션으로 변환하여 실행시킬 수 있습니다)

  5. 액션 설정 (2가지 접근 방식):

    화면 상단의 Actions 탭으로 이동하여 화면 하단의 Add Action을 탭합니다. 액션 리스트가 표시되므로, 본인의 용도에 맞춰 다음 방법 중 하나를 선택하십시오.

    • 【방법 A】 FRep2 매크로로 연결 (이미지 인식 탭 / 바로가기 이용)

      「6-2」에서 미리 만들어 둔 FRep2 매크로를 물리 버튼으로 호출합니다.

      1. 액션 리스트에서 **Launch app shortcut**을 선택합니다.

      2. 바로가기 리스트가 표시되므로, **FRep2 바로가기**를 선택합니다.

      3. FRep2 설정 화면이 열립니다. 작성한 매크로는 「창고」에 있으므로, 리스트를 **창고(비표시)**로 변경하여 해당 매크로를 선택합니다.

      4. 재생 항목은 **처음부터 재생**을 선택합니다.

        (※ 이 방법은 Shizuku 설정 없이도 작동하지만, Shizuku가 활성화된 환경이 응답 속도가 더 빠릅니다)

    • 【방법 B】 Shell command 이용 (ADB 하드 탭)

      ※ 여기서는 예시로 Uber Eats의 「거절」 액션을 가정하고 진행합니다. 다른 용도일 경우 「6-5. 커스터마이즈」를 참고하여 변경하십시오.

      1. 액션 리스트에서 **Shell command**를 선택합니다.

      2. Shell command 설정 화면이 열리면, 우선 Description이라고 적힌 칸에 제목을 입력합니다(예: Uber 거절 등).

      3. 다음으로 Script 칸 안에 아래 내용을 복사하여 붙여넣으십시오.

        # 1. 화면 사이즈 획득
        size=$(wm size | awk '{print $NF}'); w=${size%x*}; h=${size#*x}
        x=$((w * 86 / 100))
        
        # 2. 좌표 계산 (자주 사용하므로 변수에 넣습니다)
        y1=$((h * 565 / 1000))
        y2=$((h * 528 / 1000))
        y3=$((h * 491 / 1000))
        y4=$((h * 454 / 1000))
        y5=$((h * 417 / 1000))
        
        # 3. 「움직이지 않는 스와이프」 실행 (75밀리초 = 0.075초 누름)
        # tap이 아닌 swipe를 사용하고, 시작점과 끝점을 동일하게 함으로써 「길게 누르기 탭」이 됩니다
        input swipe $x $y1 $x $y1 75; sleep 0.2
        input swipe $x $y2 $x $y2 75; sleep 0.2
        input swipe $x $y3 $x $y3 75; sleep 0.2
        input swipe $x $y4 $x $y4 75; sleep 0.2
        input swipe $x $y5 $x $y5 75
        
      4. 하단에 있는 Execution Mode 설정을 **ADB**로 변경합니다.

        (※ 중요: ADB는 Shizuku와의 연동이 구축되어 있지 않으면 작동하지 않습니다. 동시에 Uber Eats 등의 앱은 「Standard」 모드에서는 탭할 수 없습니다)

      5. 설정이 완료되면, 오른쪽 하단에 있는 **Done**을 탭하여 Shell command를 저장합니다.

      6. 다음으로 액션 설정 화면으로 돌아오면, 다시 오른쪽 하단에 있는 **Done**을 눌러 액션을 저장합니다.

  6. 볼륨 변경 취소 (매우 중요) 및 저장:

    규칙 설정 화면 하단, 또는 설정(Settings) 내에 있는 **「Bypass(바이패스)」**에 체크가 되어 있는지(활성화되어 있는지) 확인합니다. 이를 통해 「트리거 버튼을 눌러도 실제 단말기의 볼륨은 변하지 않는」 상태를 만듭니다. 확인이 완료되면, 화면 오른쪽 하단의 **Done**을 탭하여 설정을 저장합니다.

  7. Key Mapper 실행 확인 (중요):

    Key Mapper가 올바르게 작동하려면 2개의 실행 스위치가 모두 ON 상태여야 합니다.

    • 개별 매크로 실행: 저장한 매크로 리스트 오른쪽 상단의 **Enabled**가 ON인지 확인.

    • Key Mapper 자체 실행: 홈 화면 상단의 **Running ↔️ Paused**가 Running 상태인지 확인.

      둘 다 ON 상태가 아니면 물리 버튼을 눌러도 반응하지 않습니다.

동일한 절차로 「버튼 2(볼륨 다운)」나 「동시 누르기(재생/일시정지)」에 대한 규칙도 각각 생성하십시오.

6-5. 커스터마이즈: 나만의 최강 도구로

처음이신 분들도 여기까지 한 번 설정하고 나면 전체적인 구조가 어느 정도 이미지화되었을 것입니다. 지금부터는 용도에 따른 커스터마이즈 힌트입니다.

  • 트리거 및 액션 변경: ”트리거” 설정 시 다른 버튼으로 설정하거나, ”액션” 설정 시 애플리케이션 실행(예: Google 지도 실행)으로 설정하는 등, 액션 리스트를 변경하여 다양하게 시도해 보십시오.

  • Shell command의 좌표 조정: 이번 방법 B의 코드에서는 화면 사이즈에 대해 % (퍼센트)로 X, Y 좌표를 계산하고 있습니다. 수치를 변경하면 탭 위치를 자유롭게 바꿀 수 있으며, % 지정이므로 단말기를 교체해도 대응하기 쉽습니다. 물론 픽셀(px)로 직접 지정하는 것도 가능합니다.

  • 처리 속도의 우위성: Key Mapper 표준의 X, Y 좌표 탭 기능을 사용하는 것보다, Shell command로 좌표를 설정하여 탭하는 것이 확실히 처리가 빠릅니다.

  • 오퍼 수락 액션에 대하여: 탭이나 스와이프 설정을 응용하면 오퍼의 「수락」도 가능하지만, 오수락(실수)의 위험이 높기 때문에 본 가이드에서는 권장하지 않으며 설정 예시도 생략했습니다. 꼭 필요한 분은 본인 책임하에 커스터마이즈하십시오.

  • 추천 설정: 주행 중에 오퍼를 받은 후, 「내비게이션 앱(Google 지도 등)으로 복귀」하는 액션을 다른 버튼에 설정해 두는 것을 매우 추천합니다.

7. 운용상의 주의점과 한계 (방수성에 대하여)

본 디바이스는 가혹한 현장에서의 사용을 상정하고 있지만, 구조적인 한계가 있습니다.

완전 방수가 아닙니다.

기판 전용 코팅 스프레이나 매니큐어를 도포함으로써 기판 실장면 자체의 방수성을 높이는 것은 가능합니다. 하지만 충전용 USB Type-C 단자가 노출되어 있으므로, 전체를 완전히 밀폐할 수는 없습니다.

우천 시에 사용할 경우에는 반드시 오토바이 핸들 커버 내에 설치하거나, 혹은 지붕이 있는 환경에서 운용하는 것을 대전제로 하십시오. 단자 부분으로 침수되면 즉시 쇼트가 발생하여 파괴됩니다.

8. 오리지널 3D 프린트용 STL 데이터 100엔 판매 안내

본 기사에서 소개한 케이스의 **3D 프린트용 데이터(STL 파일)**를 BOOTH에서 판매하고 있습니다. 기판과 배터리가 밀리미터 단위의 오차 없이 딱 맞게 수납되도록 전용 설계된 데이터입니다.

3D 프린터를 보유하신 분들은 데이터를 다운로드하여 직접 출력해 활용해 보세요.


【구매 전 주의사항】 이 케이스는 본 가이드에서 지정한 부품 구성(특히 Kailh Choc Brown과 1.8mm 두께의 젤 양면테이프)에 최적화하여 치수를 산출했습니다. 다른 스위치나 두께가 다른 테이프 등 제작 환경이 다를 경우 부품이 제대로 수납되지 않아 사용할 수 없을 가능성이 있으니 주의해 주십시오.

9. 무한한 가능성: 다음 해킹을 향하여

이 버튼은 원래 음식 배달 서비스에서의 '주문 수락', '거절'과 같은 조작을 우천 시에도 화면을 보지 않고 즉시 처리하기 위해 설계되었습니다. 하지만 범용적인 '물리 트리거'를 손에 넣음으로써 그 응용 범위는 무한히 넓어집니다.

  • AI 즉시 호출: ChatGPT 등의 음성 입력 모드나 API를 순식간에 실행하는 물리 스위치로.

  • IoT·스마트 홈 조작: Tasker 등과 연동하여 원격으로 특정 가전제품을 조작하거나 Webhook을 호출하는 트리거로.

  • 게이밍: 독자적인 입력 지연 방지 코드를 통한 특정 게임 전용 커스텀 컨트롤러로.

비 오는 날의 가혹한 도로 위, 혹은 심야의 책상 위에서 진행된 납땜과 코딩의 시행착오. 그 끝에 완성된 이 작은 버튼이 여러분의 현장 과제를 해결하고 새로운 '놀이'의 씨앗이 되기를 바랍니다.

끝까지 이 투박한 기록을 읽어주셔서 감사합니다. 이 정보가 여러분의 환경을 바꾸는 힌트나 새로운 해킹의 불씨가 된다면 본연의 역할을 다한 것입니다. 그럼, 다음 아이디어로 다시 만나 뵙겠습니다.