1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

磁気スイッチ搭載キーボードのキー押し込み量を取得してゲームパッドとして扱う

1
Last updated at Posted at 2026-02-28

1. はじめに

 新しいキーボードAIM1 瞬を買いました。最近のキーボードはラピッドトリガーなる機能が搭載されているものが増えてきており、このAIM1瞬にも搭載されています。これはキーが押し込まれて戻ったときに、設定した長さ(例えば0.1mm)のキー上昇を検知すると即座にキー入力解除とみなすものです。
 この機能の実現のため、ラピッドトリガー機能搭載キーボードにはキーストロークをアナログ量として計測可能なセンサー(磁気スイッチなど)が搭載されており、AIM1瞬の場合、キーストロークを設定ソフトから見ることができます。
image.png

 ここで思いつきました。通常のキーボード入力はON/OFFの二値入力ですが、ゲームパッドのスティックのようにアナログ入力が可能になれば、レースゲームやFPSでより細やかな操作ができるようになるはずです。

先に結果から。
実際の動作の様子がこちらです。押し込み量に比例してステアリングが切れています。

2. 先行例調査

調べたらそのままな機能がありました。しかし、メーカー独自ソフトのようで、別のキーボードには適用できません。

PC Watch: アナログ入力対応のメカニカルスイッチ搭載ゲーミングキーボード
「Wooting oneは、アナログ入力対応のメカニカルスイッチを搭載したテンキーレスキーボード。通常のメカニカルスイッチは、オン/オフの2値でキーの入力を判定しているが、本キーボードではキーが押されていない状態から底まで押されている状態を0~100のアナログ入力として認識できる。

 これにより、カーレースなどキーボードでは操作感に難があったゲームや、シューティングなどでゆっくりとキャラクターを移動させるなどの従来は実現が難しかった操作をキーボードで実現できるとする。」

3. 実装

 磁気式スイッチ搭載キーボードのWASDキーのストロークを取得し、仮想ゲームパッドを作成して左スティックに割り当てます。以下の流れで実装しました。

(1)キーボードのHIDデバイスパスの特定
(2)キー入力、キーストローク量の取得
(3)仮想ゲームパッドの作成
(4)ゲームへの適用

3-0. Python仮想環境と必要モジュールのインストール

Pythonで実装するので適当に環境構築

conda create -n gamepad python=3.11
conda activate gamepad
pip install jupyterlab

pip install hidで入れたモジュールはhidapiをロードできない問題が起こったため、以下の記事を参考にインストールしました。
Stack Overflow: Can't load hidapi with Python library "hid" on Windows

pip install hidapi Cython
git clone https://github.com/trezor/cython-hidapi.git  
cd cython-hidapi  
git submodule update --init    
python setup.py build 
python setup.py install   
pip install -e .

仮想ゲームパッド作成に必要なものも入れます。
ViGEmBusインストール
https://github.com/ViGEm/ViGEmBus/releases

pip install vgamepad

3-1. キーボードのHIDデバイスパスの特定

まずキーボードのHIDパスを特定します。

for d in hid.enumerate():
    # if d['product_string'] == 'Gaming Keyboard':
    print(d['product_string'])
    print(d['usage_page'])
    print(d['path'])
    print()

実行すると色々と出てきます。この中でGaming Keyboardと書かれたもののパスを控えておきます。

Pulsar Xlite V3 Wired Medium
1
b'\\\\?\\HID#VID_3710&PID_1401&MI_01&Col01#8&2284b81a&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}\\KBD'

Gaming Keyboard
1
b'\\\\?\\HID#VID_3151&PID_5029&MI_00#8&169d358d&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}\\KBD'

Gaming Keyboard
12
b'\\\\?\\HID#VID_3151&PID_5029&MI_01&Col01#8&3a74734f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}'

Gaming Keyboard
1
b'\\\\?\\HID#VID_3151&PID_5029&MI_01&Col02#8&3a74734f&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}'

Gaming Keyboard
65535
b'\\\\?\\HID#VID_3151&PID_5029&MI_01&Col05#8&3a74734f&0&0004#{4d1e55b2-f16f-11cf-88cb-001111000030}'

Gaming Keyboard
65535
b'\\\\?\\HID#VID_3151&PID_5029&MI_02#8&2a7ebae1&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}'

Pulsar Xlite V3 Wired Medium
1
b'\\\\?\\HID#VID_3710&PID_1401&MI_01&Col02#8&2284b81a&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}'

Pulsar Xlite V3 Wired Medium
12
b'\\\\?\\HID#VID_3710&PID_1401&MI_01&Col03#8&2284b81a&0&0002#{4d1e55b2-f16f-11cf-88cb-001111000030}'

Pulsar Xlite V3 Wired Medium
1
b'\\\\?\\HID#VID_3710&PID_1401&MI_02#8&ac12bd5&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}\\KBD'

Gaming Keyboard
1
b'\\\\?\\HID#VID_3151&PID_5029&MI_01&Col03#8&3a74734f&0&0002#{4d1e55b2-f16f-11cf-88cb-001111000030}\\KBD'

Pulsar Xlite V3 Wired Medium
65424
b'\\\\?\\HID#VID_3710&PID_1401&MI_03#8&2e986997&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}'

Pulsar Xlite V3 Wired Medium
1
b'\\\\?\\HID#VID_3710&PID_1401&MI_01&Col04#8&2284b81a&0&0003#{4d1e55b2-f16f-11cf-88cb-001111000030}'

Pulsar Xlite V3 Wired Medium
1
b'\\\\?\\HID#VID_3710&PID_1401&MI_01&Col05#8&2284b81a&0&0004#{4d1e55b2-f16f-11cf-88cb-001111000030}'

 以下のようにHIDデバイス出力を読み取ります。
 ここで、AIM1瞬は純正ソフトからキーストロークを可視化するトグルをONにしておかないとデータを取得できませんでした。自分でこのトリガーを送信するところまでやるのは面倒なので、キーストロークを取得するときは純正ソフトを起動しておくことにします。

import hid
import time

# メーカー純正ソフトを起動してSimulation demonstrationをONにしている状態なら見れる
path = b'\\\\?\\HID#VID_3151&PID_5029&MI_01&Col05#8&3a74734f&0&0004#{4d1e55b2-f16f-11cf-88cb-001111000030}'
# Gaming Keyboardを全部試す

h = hid.device()
h.open_path(path)
h.set_nonblocking(True)

while True:
    data = h.read(64)
    if data:
        print(f'\r {len(data)} {data}', end='')
    time.sleep(0.001)

実行するとこのようなデータが得られます。次にこのデータの意味を探ります。

32 [5, 27, 100, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

3-2. キー入力、キーストローク量の取得

データを表示させながら、キーの押し込み量を変えてみます。すると次のようになりました。

32 [5, 27, 14, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 41, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 68, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 89, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 110, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 170, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 230, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 250, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 4, 1, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 14, 1, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 44, 1, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 74, 1, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
32 [5, 27, 84, 1, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

ここから、3要素目が押し込み量に対応しており、255を超えると4要素目が1になることがわかります。
従って、キーストロークは次のように計算できます。

key_stroke = data[2] + data[3]*255

値の範囲は0-349となっていました。ストロークは約4mmなので、分解能は4mm/350≒0.011mmとなり、メーカー謳い文句の「キー入力のオン/オフ切替位置を0.01mm単位で調整可能」と符合します。合っていそうです。

次に入力キーを変えてデータを見ます。

# a を押しているとき
32 [5, 27, 110, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# b を押しているとき
32 [5, 27, 225, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

すると5要素目が変化しています。従って、データの5要素目を取得すれば何のキーを入力したかがわかります。

ここで入力と値のマップを作っておきます。

key_map = {
    9:'a',
    14:'w',
    15:'s',
    21:'d',
    5:'Left Ctrl',
    41:'Space',
}

これでAIM瞬からキー入力とキーストロークが得られるようになりました。

def encode_keydata(data):
    key_stroke = data[2] + data[3]*255
    pushed_key = key_map.get(data[4], None)
    return pushed_key, key_stroke

3-3. 仮想ゲームパッドの作成

後は、キーストロークとジョイスティックの値の範囲を揃えて仮想ゲームパッドを作成するだけです。コードを貼ります。

import hid
import time
import vgamepad as vg

path = b'\\\\?\\HID#VID_3151&PID_5029&MI_01&Col05#8&3a74734f&0&0004#{4d1e55b2-f16f-11cf-88cb-001111000030}'

key_map = {
    9:'a',
    14:'w',
    15:'s',
    21:'d',
    5:'Left Ctrl',
    41:'Space',
}

def encode_keydata(data):
    key_stroke = data[2] + data[3]*255
    pushed_key = key_map.get(data[4], None)
    return pushed_key, key_stroke

def norm(v):
    # 押し込み値 → スティック値変換
    # 押し込み量 0 - 349
    # スティック値 -32768 ~ 32767
    return int((v / 349) * 32767)

h = hid.device()
h.open_path(path)
h.set_nonblocking(True)

pad = vg.VX360Gamepad()

axes = {"x":0, "y":0}

while True:
    data = h.read(64)
    if data:
        key, val = encode_keydata(data)

        if key == "a":
            axes["x"] = -norm(val)
        elif key == "d":
            axes["x"] = norm(val)
        elif key == "w":
            axes["y"] = -norm(val)
        elif key == "s":
            axes["y"] = norm(val)
        else:
            pass

        pad.left_joystick(x_value=axes["x"], y_value=axes["y"])
        pad.update()

    time.sleep(0.001)

3-4. ゲームへの適用

 今回はBattleField4に適用します。オフラインのみで検証を行っていますが、オンラインではアンチチートに検知されるかもしれません。試す場合は自己責任でお願いします。

 ここでは一例として、乗り物の加速にWキーを割り当ててみます。
ゲームを起動したらキーバインドに進み、ジョイスティック欄の設定したい項目である加速をクリックしてWを押します。すると軸0Yなどとジョイスティック扱いのキーが設定されます。競合を防ぐため、キーボードのバインドは解除しておきます。

image.png

実際の動作の様子がこちらです。押し込み量に比例してステアリングが切れています。

4. まとめ

 磁気スイッチ搭載キーボードのキーストロークをアナログ量として取得し、仮想ゲームパッドの左スティックとして動作するように実装しました。
 少し細かい操作ができるようになりました。それなりに便利なので、今後、各ゲーミングキーボードメーカーが類似の機能を搭載してくれると嬉しいですね

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?