Usage
目的別の利用例です。API の引数と例外は docs/api.md、実機条件は docs/hardware.md にあります。実機接続には専用 USB Bluetooth dongle、Bumble、対象機器側の pairing / reconnect 操作が必要です。
Minimal Example
初回実行では allow_pairing=True を付け、対象機器を controller pairing / search 画面に置きます。
import asyncio
from swbt import Button, ProController
async def main() -> None:
async with ProController(
adapter="usb:0",
key_store_path="switch-bond.json",
) as pad:
await pad.connect(timeout=30.0, allow_pairing=True)
await pad.tap(Button.A)
await pad.neutral()
asyncio.run(main())
async with は終了時に close(neutral=True) 相当で閉じます。neutral() は state update API であり、即時送信を保証しません。接続中は後続の periodic report で反映されます。
Connection
First-Run Pairing Or Reconnect Fallback
async with ProController(
adapter="usb:0",
key_store_path="switch-bond.json",
) as pad:
await pad.connect(timeout=30.0, allow_pairing=True)
connect() は保存済み bond があれば reconnect を先に試します。bond がない場合、allow_pairing=True のときだけ pairing へ進みます。
Pairing Only
async with ProController(
adapter="usb:0",
key_store_path="switch-bond.json",
) as pad:
await pad.pair(timeout=30.0)
pair() は対象機器が controller pairing / search 画面にいるときだけ使います。
Reconnect Only
async with ProController(
adapter="usb:0",
key_store_path="switch-bond.json",
) as pad:
await pad.reconnect(timeout=10.0)
reconnect() は key store に current bonded peer が 1 件ある状態だけを扱います。pairing fallback はしません。
Handling Result Values
async with ProController(
adapter="usb:0",
key_store_path="switch-bond.json",
) as pad:
result = await pad.try_connect(timeout=30.0, allow_pairing=True)
if result.status != "connected":
print(result.status, result.route, result.peer_count)
async with ProController(
adapter="usb:0",
key_store_path="switch-bond.json",
) as pad:
result = await pad.try_reconnect(timeout=10.0)
if result.status == "no_bond":
print("pairing is required")
try_connect() / try_reconnect() は接続戦略の結果を ConnectionResult で返します。key store の形式不一致や複数 current peers は InvalidKeyStoreError として扱います。
Separate Key Stores By Target Device And Profile
first = ProController(
adapter="usb:0",
key_store_path="switch-2-fw-22-1-0.json",
)
second = ProController(
adapter="usb:0",
key_store_path="other-switch.json",
)
1 つの key store に複数の current peer を混ぜないでください。別の対象機器へ pairing したい場合は、対象機器ごとに別の key_store_path を使います。Pro Controller、Joy-Con L、Joy-Con R のように profile が違う場合も、同じ対象機器で key store を共有しません。
Single Joy-Con L/R
単体 Joy-Con 相当の仮想デバイスは JoyConL(...) または JoyConR(...) で作ります。どちらも SwitchGamepad interface を満たし、connect()、pair()、reconnect()、入力 API、close(neutral=True) の契約は同じです。
Left Joy-Con
import asyncio
from swbt import Button, JoyConL, Stick
async def main() -> None:
async with JoyConL(
adapter="usb:0",
key_store_path="switch-left-joycon-bond.json",
) as left:
await left.connect(timeout=30.0, allow_pairing=True)
await left.tap(Button.SR, Button.SL)
await left.tap(Button.L)
await left.lstick(Stick.left())
await left.neutral()
asyncio.run(main())
左 Joy-Con では D-pad、L/ZL、MINUS、CAPTURE、SL/SR、left stick を使います。
Right Joy-Con
import asyncio
from swbt import Button, JoyConR, Stick
async def main() -> None:
async with JoyConR(
adapter="usb:0",
key_store_path="switch-right-joycon-bond.json",
) as right:
await right.connect(timeout=30.0, allow_pairing=True)
await right.tap(Button.A)
await right.rstick(Stick.right())
await right.neutral()
asyncio.run(main())
右 Joy-Con では A/B/X/Y、R/ZR、PLUS、HOME、SL/SR、right stick を使います。
Unsupported Inputs
片側 Joy-Con が持たない button や stick は UnsupportedInputError になります。
from swbt import Button, JoyConL, Stick, UnsupportedInputError
async with JoyConL(
adapter="usb:0",
key_store_path="switch-left-joycon-bond.json",
) as left:
try:
await left.rstick(Stick.right())
except UnsupportedInputError as error:
print(error)
try:
await left.tap(Button.A)
except UnsupportedInputError as error:
print(error)
InputState + apply() でも同じ検査を行います。左 Joy-Con に right stick、右 Joy-Con に left stick や D-pad を含めると UnsupportedInputError です。
Change Grip/Order 画面で単体 Joy-Con として順番登録する場合は、接続後に await left.tap(Button.SR, Button.SL) のように SR+SL を送ります。
左右ペアの JoyConPair は未実装です。左右を 1 つの controller として扱う API は別 issue の範囲です。
2026-07-06 の Joy-Con L 実機観測では、HID 通信上の device name と device-info reply が Joy-Con L になり、SDP policy 反映後に Switch UI で Joy-Con として登録されたことをユーザ目視で確認しました。Joy-Con R、reconnect、Joy-Con profile の通常入力反映、SDP 完全一致、OS / dongle / firmware をまたぐ互換性は未検証です。Joy-Con L/R を実機で試す場合は、Pro Controller 相当で確認済みの結果とは分けて記録してください。
Button Input
Tap A
await pad.tap(Button.A)
tap() は action API です。接続済みを要求し、押下と release の input report を即時送信します。
Hold ZL And Tap A
await pad.press(Button.ZL)
await pad.tap(Button.A)
await pad.release(Button.ZL)
tap(Button.A) は A だけを release します。事前に press(Button.ZL) した ZL は維持されます。
Hold L+R Then Release
import asyncio
from swbt import Button
await pad.press(Button.L, Button.R)
await asyncio.sleep(0.5)
await pad.release(Button.L, Button.R)
await pad.neutral()
press() / release() は state update API です。接続を要求せず、即時送信を保証しません。
Stick Input
Tilt The Left Stick
from swbt import Stick
await pad.lstick(Stick.up())
await pad.lstick(Stick.up(0.5))
lstick() は left stick だけを置き換える state update API です。Stick.up() は全倒し、Stick.up(0.5) は半倒しです。
Tilt The Right Stick
from swbt import Stick
await pad.rstick(Stick.right())
rstick() は right stick だけを置き換えます。
Arbitrary Stick Coordinates
from swbt import Stick
await pad.sticks(left=Stick.tilt(0.7, 0.7))
Stick.tilt(x, y) は Stick.normalized(x=x, y=y) と同じ -1.0..1.0 の正規化座標を使う短い生成 API です。sticks()、lstick()、rstick() は Stick だけを受けます。tuple や raw tuple は受けません。
Press B And Tilt The Left Stick
複数の state update API 呼び出しは、同一 HID report に入る保証はありません。
await pad.press(Button.B)
await pad.lstick(Stick.up())
完全同時入力が必要な場合は、complete InputState を作って apply() します。
from swbt import Button, InputState, Stick
state = InputState.neutral().with_buttons([Button.B]).with_sticks(
left_stick=Stick.up(),
)
await pad.apply(state)
apply() は現在入力全体を置き換えます。差分適用ではありません。
IMU Input
Update Gyro Only
from swbt import IMUFrame
await pad.imu(IMUFrame.gyro(100, 0, 0))
imu() は IMU だけを置き換える state update API です。imu(frame) は 3 frame すべてに同じ値を設定します。即時送信を保証しません。
Set Accel And Gyro In One Frame
from swbt import IMUFrame
frame = IMUFrame.accel(0, 0, 4096).with_gyro(100, 0, 0)
await pad.imu(frame)
IMUFrame.accel(0, 0, 4096).with_gyro(100, 0, 0) は accel を設定した frame に gyro を追加します。IMUFrame.raw(accel=(0, 0, 4096), gyro=(100, 0, 0)) と同じ値です。
Three IMU Frames
from swbt import IMUFrame
await pad.imu(
IMUFrame.gyro(100, 0, 0),
IMUFrame.gyro(120, 0, 0),
IMUFrame.gyro(140, 0, 0),
)
3 frame を渡した場合は順に設定します。0 個、2 個、4 個以上、IMUFrame 以外は InvalidInputError です。
Complete State With Button, Stick, And IMU
from swbt import Button, IMUFrame, InputState, Stick
state = (
InputState.neutral()
.with_buttons([Button.B])
.with_sticks(left_stick=Stick.up())
.with_accel((0, 0, 4096))
.with_gyro((100, 0, 0))
)
await pad.apply(state)
with_accel((0, 0, 4096)) と with_gyro((100, 0, 0)) は 1 sample を 3 frame すべてに複製します。3 sample を渡すと、各 frame の accel または gyro を順に置き換えます。button、stick、IMU を同じ complete state として扱いたい場合は InputState + apply() を使います。
Neutral And Close
await pad.neutral()
neutral() は local state を neutral に戻します。即時送信を保証しません。
await pad.close(neutral=True)
close(neutral=True) は接続中なら trailing neutral を試みてから transport を閉じます。async with の scope 終了時に同じ処理が走るため、scope の最後で重ねて呼ぶ必要はありません。
Diagnostics
from pathlib import Path
from swbt import DiagnosticsConfig, ProController
with Path("trace.jsonl").open("w", encoding="utf-8") as trace:
async with ProController(
adapter="usb:0",
key_store_path="switch-bond.json",
diagnostics=DiagnosticsConfig(trace_writer=trace),
) as pad:
await pad.connect(timeout=30.0, allow_pairing=True)
status = pad.status()
print(status.connection_state)
DiagnosticsConfig(trace_writer=trace) は JSON Lines trace を出します。pad.status() は connection state、report counter、last subcommand、raw rumble、last error を返します。
Scope
初期 public API には duration 付き保持入力、入力列 runner、公開の現在入力送信 API、fluent builder API はありません。入力列や macro runner が必要になった場合は、利用者側で asyncio と公開 API を組み合わせてください。