Virtual Nintendo Switch Pro Controller as a Python library.
Two backends behind the same PPCController facade:
- nxbt — real hardware over Bluetooth on Linux (BlueZ + nxbt).
- mock — logs every call instead of driving real hardware. Testing and development only.
On top of the facade, two sub-packages:
pyprocontroller.adapter— read events from a real USB/Bluetooth gamepad (e.g. a DualSense) and forward them to the Switch in real time.pyprocontroller.macro— record those events to JSON and replay them later as scripts.
A bundled adapter.py CLI tool ties them together (relay, record, replay). See USAGE.md for the day-to-day recipes — this README only covers install + library API.
git clone https://github.com/AndrSator/PyProController.git
cd PyProControllerThen pick one path.
Mock backend only (testing/development, no Switch needed):
python3 -m venv .venv
source .venv/bin/activate
pip install -e .Real Switch backend (Linux only):
# 1. Configure bluetoothd to expose the HID profile.
sudo apt install python3-dbus
sudo systemctl edit --full bluetooth.service
# in the editor, change ExecStart to: /usr/sbin/bluetoothd -C -P input
sudo systemctl restart bluetooth
# 2. Create venv with --system-site-packages and install nxbt + pinned deps.
python3 -m venv .venv --system-site-packages
source .venv/bin/activate
pip install -e . Flask==1.1.2 Flask-SocketIO==5.0.1 eventlet==0.31.0 \
blessed==1.17.10 pynput==1.7.1 psutil cryptography==3.3.2
pip install nxbt --no-depsReal-Switch commands need root: sudo .venv/bin/python adapter.py relay.
from pyprocontroller import PPCController, Button, Stick
with PPCController() as ctrl: # auto-selects backend
ctrl.press([Button.A])
ctrl.press([Button.B], down=0.2)
ctrl.tilt_stick(Stick.LEFT, 100, 0, tilted=1.0)
ctrl.macro("B 0.1s\n A 0.1s")Force a backend explicitly:
PPCController(backend="mock") # always the mock, even on Linux
PPCController(backend="nxbt") # error out instead of falling backFor real-time relay or replay, use set_state instead of the timed methods:
from pyprocontroller import Button
with PPCController() as ctrl:
ctrl.set_state({Button.A, Button.DPAD_UP}, left_stick=(80, 0))
# nxbt keeps sending this packet every cycle until the next set_state.