Skip to content

Convert a Raspberry Pi into a HID proxy that relays Bluetooth keyboard and mouse input to USB. Minimal configuration. Zero hassle.

License

Notifications You must be signed in to change notification settings

ig-sinicyn/bluetooth_2_usb

 
 

Repository files navigation

Bluetooth to USB

Connection overview

Table of Contents

1. Introduction

Convert a Raspberry Pi into a HID proxy that relays Bluetooth keyboard and mouse input to USB. Minimal configuration. Zero hassle.

The issue with Bluetooth devices is that you usually can't use them to wake up sleeping devices, access the BIOS or OS select menu (GRUB). Some devices don't even have a (working) Bluetooth interface.

Sounds familiar? Congratulations! You just found the solution!

2. Features

  • Simple installation and highly automated setup
  • Supports multiple input devices (currently keyboard and mouse - more than one of each kind simultaneously)
  • Supports 146 multimedia keys (e.g. mute, volume up/down, launch browser, etc.)
  • Auto-reconnect feature for input devices (power off, energy saving mode, out of range, etc.)
  • Robust error handling and logging
  • Installation as a systemd service
  • Reliable concurrency using state-of-the-art TaskGroups
  • Clean and actively maintained code base

3. Requirements

  • (Single-board) computer with Bluetooth support, e.g. Raspberry Pi 4B or Raspberry Pi Zero W
  • Linux OS with systemd support, e.g. Raspberry Pi OS (recommended)
  • Python 3.11 for using TaskGroups

4. Installation

Follow these steps to install and configure the project:

4.1. Prerequisites

  1. Install an OS on your Raspberry Pi (e.g. using Pi Imager)

  2. Connect to a network via Ethernet cable or Wi-Fi. Make sure this network has Internet access.

  3. (optional) Enable SSH, if you intend to access the Pi remotely.

Note

These settings above may be configured during imaging, on first boot or afterwards.

  1. Connect to the Pi and make sure git and python3.11 are installed:

    sudo apt update && sudo apt upgrade -y && sudo apt install -y git python3.11
  2. Pair and trust any Bluetooth devices you wish to relay, either via GUI or via CLI:

    bluetoothctl
    scan on

    ... wait for your devices to show up and note their MAC addresses (you may also type the first characters and hit TAB for auto-completion in the following commands) ...

    pair A1:B2:C3:D4:E5:F6
    trust A1:B2:C3:D4:E5:F6

    [!NOTE] Replace A1:B2:C3:D4:E5:F6 by your input device's Bluetooth MAC address

4.2. Setup

  1. On the Pi, clone the repository:

    git clone https://github.com/quaxalber/bluetooth_2_usb.git
  2. Navigate to the project folder:

    cd bluetooth_2_usb
  3. Init the submodules:

    git submodule update --init --recursive
  4. Check which Linux input devices your Bluetooth devices are mapped to:

    python3.11 bluetooth_2_usb.py -l

    ... and note the device paths of the devices you want to use:

    user@raspberrypi:~/bluetooth_2_usb $ python3.11 bluetooth_2_usb.py -l
    AceRK Mouse     0a:1b:2c:3d:4e:5f  /dev/input/event3  <---
    AceRK Keyboard  0a:1b:2c:3d:4e:5f  /dev/input/event2  <---
    vc4-hdmi-1      vc4-hdmi-1/input0  /dev/input/event1
    vc4-hdmi-0      vc4-hdmi-0/input0  /dev/input/event0
  5. Specify the correct input devices in bluetooth_2_usb.service:

    nano bluetooth_2_usb.service

    ... and change event3 and event2 according to step 9.

    [!NOTE] Ctrl + X > Y > Enter to save and exit nano

  6. (optional) If you wish to test first, without actually sending anything to the target devices, append -s to the ExecStart= command to enable sandbox mode. To increase log verbosity add -d.

  7. Run the installation script as root:

sudo bash install.sh
  1. Restart the Pi (prompt at the end of install.sh)

  2. Verify that the service is running:

    service bluetooth_2_usb status

    It should look something like this:

    user@raspberrypi:~/bluetooth_2_usb $ service bluetooth_2_usb status
    ● bluetooth_2_usb.service - Bluetooth to USB HID proxy
        Loaded: loaded (/home/user/bluetooth_2_usb/bluetooth_2_usb.service; enabled; vendor preset: enabled)
        Active: active (running) since Wed 2023-10-11 18:00:58 BST; 11s ago
      Main PID: 4256 (python3.11)
          Tasks: 1 (limit: 8755)
            CPU: 328ms
        CGroup: /system.slice/bluetooth_2_usb.service
                └─4256 python3.11 /usr/bin/bluetooth_2_usb.py -k /dev/input/event2 -m /dev/input/event3
    
    Oct 11 18:00:58 raspberrypi systemd[1]: Started Bluetooth to USB HID proxy.
    Oct 11 18:00:58 raspberrypi python3.11[4256]: 23-10-11 18:00:58 [INFO] Launching Bluetooth 2 USB v0.4.4
    Oct 11 18:01:01 raspberrypi python3.11[4256]: 23-10-11 18:01:01 [INFO] Starting event loop for [device /dev/input/event2, name "AceRK Keyboard", phys "0a:1b:2c:3d:4e:5f"] >> [Keyboard gadget (/dev/hidg1) + Consumer control gadget (/dev/hidg2)]
    Oct 11 18:01:01 raspberrypi python3.11[4256]: 23-10-11 18:01:01 [INFO] Starting event loop for [device /dev/input/event3, name "AceRK Mouse", phys "0a:1b:2c:3d:4e:5f"] >> [Boot mouse gadget (/dev/hidg0)]

Note

Something seems off? Try yourself in Troubleshooting!

5. Usage

Connect the power USB port of your Pi (Micro-USB or USB-C) via cable with a USB port on your target device. You should hear the USB connection sound (depending on the target device) and be able to access your target device wirelessly using your Bluetooth keyboard or mouse.

Important

It's essential to use the small power port instead of the bigger USB-A ports, since only the power port has the OTG feature required for USB gadgets.

5.1. Command-line arguments

Currently you can provide the following CLI arguments:

user@raspberrypi:~/bluetooth_2_usb $ python3.11 bluetooth_2_usb.py -h
usage: bluetooth_2_usb.py [-h] [--keyboards KEYBOARDS] [--mice MICE] [--sandbox] [--debug] [--log_to_file] [--log_path LOG_PATH] [--version]

Bluetooth to USB HID proxy. Reads incoming mouse and keyboard events (e.g., Bluetooth) and forwards them to USB using Linux's gadget mode.

options:
  -h, --help            show this help message and exit
  --keyboards KEYBOARDS, -k KEYBOARDS
                        Comma-separated list of input device paths for keyboards to be registered and connected. Default is None. Example: --keyboards /dev/input/event2,/dev/input/event4
  --mice MICE, -m MICE  Comma-separated list of input device paths for mice to be registered and connected. Default is None. Example: --mice /dev/input/event3,/dev/input/event5
  --sandbox, -s         Only read input events but do not forward them to the output devices.
  --debug, -d           Enable debug mode. Increases log verbosity
  --log_to_file, -f     Add a handler that logs to file additionally to stdout.
  --log_path LOG_PATH, -p LOG_PATH
                        The path of the log file. Default is /var/log/bluetooth_2_usb/bluetooth_2_usb.log.
  --version, -v         Display the version number of this software.
  --list_devices, -l    List all available input devices and exit.

5.2. Consuming the API from your Python code

The API is designed such that it may be consumed both via CLI and from within external Python code. More details on this coming soon!

6. Troubleshooting

6.1. The Pi keeps rebooting or crashes randomly

This is likely due to the limited power the Pi gets from the host's USB port. Try these steps:

  • If available, connect your Pi to a USB 3 port on the host / target device (usually blue).

Important

Do not use the blue (or black) USB-A ports of your Pi to connect. This won't work.

Do use the small USB power port.

  • Try to connect to the Pi via SSH instead of attaching a display directly and remove any unnecessary peripherals.

  • Install a lite version of your OS on the Pi (without GUI)

  • Get a USB-C Data/Power Splitter (or Micro-USB respectively) and draw power from a sufficiently powerful power adaptor. This will ultimately resolve any power-related issues, and your Pi will no longer be dependent on the host's power supply.

Note

The Pi 4B requires 3A/15W for stable operation!

6.2. The installation was successful, but I don't see any output on the target device

This could be due to a number of reasons. Try these steps:

  • Verify that the service is running:

    service bluetooth_2_usb status
  • Verify that you specified the correct input devices in bluetooth_2_usb.service and that sandbox mode is off (that is no --sandbox or -s flag)

  • Verify that your Bluetooth devices are paired, trusted, connected and not blocked:

    bluetoothctl
    info A1:B2:C3:D4:E5:F6

    It should look like this:

    user@raspberrypi:~/bluetooth_2_usb $ bluetoothctl
    Agent registered
    [CHG] Controller 0A:1B:2C:3D:4E:5F Pairable: yes
    [AceRK]# info A1:B2:C3:D4:E5:F6
    Device A1:B2:C3:D4:E5:F6 (random)
            Name: AceRK
            Alias: AceRK
            Paired: yes     <---
            Trusted: yes    <---
            Blocked: no     <---
            Connected: yes  <---
            WakeAllowed: no
            LegacyPairing: no
            UUID: Generic Access Profile    (00001800-0000-1000-8000-00805f9b34fb)
            UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb)
            UUID: Device Information        (0000180a-0000-1000-8000-00805f9b34fb)
            UUID: Human Interface Device    (00001812-0000-1000-8000-00805f9b34fb)
            UUID: Nordic UART Service       (6e400001-b5a3-f393-e0a9-e50e24dcca9e)

Note

Replace A1:B2:C3:D4:E5:F6 by your input device's Bluetooth MAC address

  • Reload and restart service:

    sudo systemctl daemon-reload
    sudo service bluetooth_2_usb restart
  • Reboot Pi

    sudo reboot 
  • Re-connect the Pi to the host and check that the cable is in good shape

  • Try a different USB port on the host

  • Try connecting to a different host

6.3. In bluetoothctl, my device is constantly switching on/off

This is a common issue, especially when the device gets paired with multiple hosts. One simple fix/workaround is to re-pair the device:

bluetoothctl
power off
power on
block A1:B2:C3:D4:E5:F6
remove A1:B2:C3:D4:E5:F6
scan on
pair A1:B2:C3:D4:E5:F6
trust A1:B2:C3:D4:E5:F6

If the issue persists, it's worth trying to delete the cache:

sudo -i
cd '/var/lib/bluetooth/0A:1B:2C:3D:4E:5F/cache'
rm -rf 'A1:B2:C3:D4:E5:F6'
exit

Note

Replace 0A:1B:2C:3D:4E:5F by your Pi's Bluetooth controller's MAC and A1:B2:C3:D4:E5:F6 by your input device's MAC

6.4. I have a different issue

Here's a few things you could try:

  • Check the log files (default at /var/log/bluetooth_2_usb/) for errors

Note

Logging to file requires the -f flag

  • You may also query the journal to inspect the service logs in real-time:

    journalctl -u bluetooth_2_usb.service -n 20 -f
  • Increase log verbosity by appending -d to the command in the line starting with ExecStart= in bluetooth_2_usb.service.

  • Reload and restart service:

    sudo systemctl daemon-reload
    sudo service bluetooth_2_usb restart
  • For easier degguging, you may also stop the service

    sudo service bluetooth_2_usb stop

    and run the script manually, modifying arguments as required, e.g.:

    sudo python3.11 bluetooth_2_usb.py -k /dev/input/event2 -m /dev/input/event3 -d
  • When you interact with your Bluetooth devices with -d set, you should see debug output in the logs such as:

    user@raspberrypi:~/bluetooth_2_usb $ sudo python3.11 bluetooth_2_usb.py -k /dev/input/event2 -m /dev/input/event3 -d
    23-10-20 09:50:27 [DEBUG] CLI args: Namespace(keyboards=['/dev/input/event2'], mice=['/dev/input/event3'], sandbox=False, debug=True, log_to_file=False, log_path='/var/log/bluetooth_2_usb/bluetooth_2_usb.log', version=False)
    23-10-20 09:50:27 [DEBUG] Logging to stdout
    23-10-20 09:50:27 [INFO] Launching Bluetooth 2 USB v0.4.4
    23-10-20 09:50:27 [DEBUG] Available output devices: [Boot mouse gadget (/dev/hidg0), Keyboard gadget (/dev/hidg1), Consumer control gadget (/dev/hidg2)]
    23-10-20 09:50:30 [DEBUG] Sandbox mode disabled. All output devices activated.
    23-10-20 09:50:30 [DEBUG] Registered device link: [AceRK Keyboard]>>[/dev/hidg1+/dev/hidg2]
    23-10-20 09:50:30 [DEBUG] Registered device link: [AceRK Mouse]>>[/dev/hidg0]
    23-10-20 09:50:30 [DEBUG] Connected device link: [AceRK Keyboard]>>[/dev/hidg1+/dev/hidg2]
    23-10-20 09:50:30 [DEBUG] Connected device link: [AceRK Mouse]>>[/dev/hidg0]
    23-10-20 09:50:30 [DEBUG] Current tasks: {<Task pending name='[AceRK Mouse]>>[/dev/hidg0]' coro=<ComboDeviceHidProxy._async_relay_input_events() running at /home/user/bluetooth_2_usb/bluetooth_2_usb.py:217> cb=[TaskGroup._on_task_done()]>, <Task pending name='[AceRK Keyboard]>>[/dev/hidg1+/dev/hidg2]' coro=<ComboDeviceHidProxy._async_relay_input_events() running at /home/user/bluetooth_2_usb/bluetooth_2_usb.py:217> cb=[TaskGroup._on_task_done()]>, <Task pending name='Task-1' coro=<_main() running at /home/user/bluetooth_2_usb/bluetooth_2_usb.py:375> cb=[_run_until_complete_cb() at /usr/local/lib/python3.11/asyncio/base_events.py:180]>}
    23-10-20 09:50:30 [INFO] Starting event loop for [device /dev/input/event2, name "AceRK Keyboard", phys "0a:1b:2c:3d:4e:5f"] >> [Keyboard gadget (/dev/hidg1) + Consumer control gadget (/dev/hidg2)]
    23-10-20 09:50:30 [INFO] Starting event loop for [device /dev/input/event3, name "AceRK Mouse", phys "0a:1b:2c:3d:4e:5f"] >> [Boot mouse gadget (/dev/hidg0)]
    23-10-20 09:50:42 [DEBUG] Received event: [event at 1697791842.883636, code 04, type 04, val 458756]
    23-10-20 09:50:42 [DEBUG] Received event: [key event at 1697791842.883636, 30 (KEY_A), down]
    23-10-20 09:50:42 [DEBUG] Converted evdev ecode 0x1E (KEY_A) to HID UsageID 0x04 (A)
    23-10-20 09:50:42 [DEBUG] Received event: [synchronization event at 1697791842.883636, SYN_REPORT]
    23-10-20 09:51:35 [DEBUG] Received event: [relative axis event at 1697791895.144049, REL_X]
    23-10-20 09:51:35 [DEBUG] Moving mouse /dev/hidg0: (x, y, mwheel) = (-125, 0, 0)
    23-10-20 09:51:35 [DEBUG] Received event: [synchronization event at 1697791895.144049, SYN_REPORT]
  • Still not resolved? Double-check the installation instructions

  • For more help, open an issue in the GitHub repository

6.5. Everything is working, but can it help me with Bitcoin mining?

Absolutely! Here's how.

7. Bonus points

After successfully setting up your Pi as a HID proxy for your Bluetooth devices, you may consider making Raspberry OS read-only. That helps preventing the SD card from wearing out and the file system from getting corrupted when powering off the Raspberry forcefully.

8. Contributing

Contributions are welcome! Please read the CONTRIBUTING.md file for guidelines.

9. License

This project is licensed under the MIT License - see the LICENSE.md file for details.

"Bluetooth 2 HID" image @PixelGordo is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.

License image.

10. Acknowledgments

About

Convert a Raspberry Pi into a HID proxy that relays Bluetooth keyboard and mouse input to USB. Minimal configuration. Zero hassle.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python 92.1%
  • Shell 7.9%