Thursday, August 7, 2025

Using Logitech FCB310 Gamepad as a Macropad on Raspberry Pi 4

This tutorial shows how to use your Logitech FCB310 USB gamepad as a macropad on a Raspberry Pi 4, with automatic startup and hotplug detection. When plugged in, your gamepad buttons will trigger keyboard macros on the Pi, no manual script launching needed.


Requirements

  • Raspberry Pi 4 with Raspberry Pi OS (desktop)
  • Logitech FCB310 USB Gamepad
  • Internet connection for installing packages

Step 1: Install Required Software

Open a terminal and run:

sudo apt update
sudo apt install python3-pip xdotool
pip3 install inputs pyudev watchdog
  • xdotool: simulates keyboard keypresses
  • inputs: reads gamepad events
  • pyudev: detects USB device plug/unplug
  • watchdog: monitors macro config file changes

Step 2: Prepare Macro Configuration File

Create a CSV file to map gamepad buttons to keyboard shortcuts.

Example file: /home/pi/macropad/macros.csv

BTN_A,ctrl+alt+t
BTN_B,ctrl+w
  • First column: gamepad button code (e.g., BTN_A)
  • Second column: key combo for xdotool (e.g., ctrl+alt+t)

Step 3: Create the Macro Script

Create a folder and the Python script:

mkdir -p ~/macropad
vim ~/macropad/gamepad_macro.py

Paste this full script into it:

import csv
import subprocess
from inputs import get_gamepad
import pyudev
import threading
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import time

mapping = {}

def load_macros():
    global mapping
    mapping = {}
    try:
        with open("/home/pi/macropad/macros.csv", newline='') as csvfile:
            reader = csv.reader(csvfile)
            for row in reader:
                if len(row) >= 2:
                    btn = row[0].strip()
                    keys = row[1].strip()
                    mapping[btn] = [keys]
        print("Macros reloaded:", mapping)
    except Exception as e:
        print("Failed to load macros:", e)

def send_macro(keys):
    for combo in keys:
        subprocess.run(["xdotool", "key", combo])

def listen_gamepad(stop_event):
    while not stop_event.is_set():
        try:
            events = get_gamepad()
            for event in events:
                if event.ev_type == "Key" and event.state == 1:
                    if event.code in mapping:
                        send_macro(mapping[event.code])
        except Exception:
            pass

class ConfigChangeHandler(FileSystemEventHandler):
    def on_modified(self, event):
        if event.src_path.endswith("macros.csv"):
            print("Config file changed, reloading macros")
            load_macros()

def device_event(observer, device):
    global gamepad_thread, stop_event
    if device.action == "add" and "event" in device.device_node:
        print("Gamepad connected, starting listener")
        load_macros()
        if gamepad_thread and gamepad_thread.is_alive():
            stop_event.set()
            gamepad_thread.join()
        stop_event.clear()
        gamepad_thread = threading.Thread(target=listen_gamepad, args=(stop_event,), daemon=True)
        gamepad_thread.start()
    elif device.action == "remove":
        print("Gamepad disconnected")
        stop_event.set()
        if gamepad_thread:
            gamepad_thread.join()

if __name__ == "__main__":
    stop_event = threading.Event()
    gamepad_thread = None

    context = pyudev.Context()
    monitor = pyudev.Monitor.from_netlink(context)
    monitor.filter_by('input')

    observer = pyudev.MonitorObserver(monitor, device_event)
    observer.start()

    # Watch macros.csv for changes
    config_handler = ConfigChangeHandler()
    config_observer = Observer()
    config_observer.schedule(config_handler, path="/home/pi/macropad/", recursive=False)
    config_observer.start()

    print("Waiting for gamepad to connect...")
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        stop_event.set()
        if gamepad_thread:
            gamepad_thread.join()
        config_observer.stop()
        config_observer.join()

Step 4: Make Script Executable

chmod +x ~/macropad/gamepad_macro.py

Step 5: Setup Autostart with systemd

Create a service file:

sudo vim /etc/systemd/system/macropad.service

Add:

[Unit]
Description=Gamepad Macro Pad Service
After=graphical.target

[Service]
ExecStart=/usr/bin/python3 /home/pi/macropad/gamepad_macro.py
Restart=always
User=pi

[Install]
WantedBy=graphical.target

Enable and start service:

sudo systemctl enable macropad.service
sudo systemctl start macropad.service

Step 6: Reboot and Test

Reboot your Pi:

sudo reboot
  • Plug in your FCB310 gamepad.
  • Press buttons mapped in macros.csv.
  • Macros should trigger automatically as keyboard inputs.

Customization

  • Edit /home/pi/macropad/macros.csv anytime.
  • The script auto-reloads macros on file changes.
  • Add/remove mappings without restarting.

Troubleshooting

  • Ensure your gamepad device shows up in /dev/input/ (use evtest to confirm).
  • Adjust key combos to fit your needs (see xdotool docs).
  • Check logs with journalctl -u macropad.service -f.

No comments:

Post a Comment