【完全版】遅延ゼロ・誤作動なし。Android用「汎用物理トリガーボタン」をゼロから自作するプロ向けガイド

【完全版】遅延ゼロ・誤作動なし。Android用「汎用物理トリガーボタン」をゼロから自作するプロ向けガイド

|ツール

この記事では、雨天時の誤作動やスリープ復帰の遅延に悩むフードデリバリー配達員、あるいはAndroid端末の操作を極限まで効率化したいギークに向けて、現場の執念が生み出した「汎用物理トリガーボタン」の完全な製作手順を解説します。

作成時のX投稿その1 作成時のX投稿その2 作成時のX投稿その3 作成時のX投稿その4 作成時のX投稿その5

1. コンセプト:なぜ「自作」に行き着いたのか

本ガイドは実践的なハードウェア・ソフトウェアの構築手順に特化していますが、この「究極の物理ボタン」が生まれるまでの、雨天の路上での度重なる失敗と試行錯誤のプロセスについては、以下のnote記事に記録しています。なぜ既存の製品ではダメだったのか、開発に至るまでの泥臭いストーリーに興味がある方はあわせてご覧ください。

市販されている安価なBluetoothボタンやメディアコントローラーには、業務レベルの過酷な環境(雨天、連続稼働、一瞬の判断を要する操作)において、致命的な弱点が存在します。

  • スリープ復帰の遅延: 省電力機能により、いざ押したい時に最初の1クリック目が認識されない。

  • レイアウト干渉: 特定のアプリ(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-)に直接ハンダ付けします。

  • ショート厳禁: リポバッテリーの配線を切断する際、絶対にプラス線とマイナス線をニッパーで同時に切らないでください。 刃を通じてショートし、発火の危険があります。必ず1本ずつ切断し、片方の作業が終わるまでもう片方はテープ等で絶縁しておきます。

  • 接続手順: 安全のため、まず**マイナス(黒線)を基板のBAT-にハンダ付けし、その後にプラス(赤線)をBAT+**にハンダ付けします。

半田付け01

はんだ付け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などのver違いがあるので注意してください。

  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);
}

  • 貼り付けができましたら画面左上にある(右矢印アイコン)で書き込みを実行してください。

    #のゲージ表示が始まり完了したらそのまま取り外してOKです。書き込み中は外さないでください。

    書き込みエラーが出た場合は設定や貼り付け不具合が発生している可能性があります。エラーコードをコピーしてから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側でこの入力をフックし、望みの動作に変換します。この設定を行わないと、ただ音量が変わるだけのボタンになってしまいます。

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. 重要:認識する枠は「小さいほど」処理が速くなります。 拒否ボタンであれば、横に細長くして×マークの中心あたりを小さめに指定すればOKです。指定できたら右下の 適用 を押します。

⑥ 認識設定と検索範囲の最適化(高速化の要)

  1. 次の画面の 【認識設定】色の類似度 をタップし、検索:類似度 に変更します。

  2. 認識領域の中央をタップ にチェックを入れます。

  3. 検索範囲:画面全体 をタップすると、4箇所に数字が入れられる図が出ます。

  4. 右上にある「画像表示(写真を指差すようなアイコン)」をタップします。

    画像の数値は液晶サイズによって変化するので参考にせず必ず手持ちの端末でサイズ調整を行ってください

  5. 先ほど青枠で設定したターゲット部分だけが明るく表示されます。

    Uber Eatsの拒否ボタンなど、ポップアップによって上下の位置が変動するボタンの場合は、その変動する最大幅に合わせて明るい領域を広げてください。

    (※ここも検索範囲が小さいほど処理が圧倒的に速くなります。必要最小限に留めてください)

  6. 適用 を押して設定します。

  7. 【高度な設定】 でスクリーンショットの保存が不要であればチェックを外します。

  8. 画面右下の 閉じる を押して画像検索設定を終了します。

  9. 編集画面の最初からある「待機 5.0」は不要なタイムロスになるため、削除してOKです。

⑦ 設定の保存と確認

  1. 画面右上の**「フロッピーディスクアイコン」**をタップして設定を保存します。

  2. 左上の戻る矢印(またはバーガーマーク)から 記録 をタップし、設定一覧に戻ります。

  3. 上部の帯にある 標準パネル をタップし、倉庫 に変更します。

  4. 先ほど作成したマクロが表示されていれば、FRep2側の準備は完了です。

6-3. Shizukuの導入と起動(システム権限確保とレスポンス向上)

通常はRoot化が必要なレベルのシステム操作(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やテザリングの接続を解除してOKです。(※端末を再起動した場合は、再度この起動手順が必要になる場合があります)

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 ↔️ PausedRunning になっているか。

      双方が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を叩くトリガーとして。

  • ゲーミング: 独自の入力遅延対策コードによる、特定ゲーム専用のカスタムコントローラーとして。

雨の日の過酷な路上、あるいは深夜のデスクでのハンダ付けとコーディングの試行錯誤。その末に完成したこの小さなボタンが、皆さんの現場の課題を解決し、新しい「遊び」の種になることを願っています。

最後までこの泥臭い記録を読んでいただき、ありがとうございます。 この情報が、あなたの環境を変えるヒントや、新たなハックの火種になれば本領発揮です。 それでは、また次のアイデアでお会いしましょう。