Skip to content

Instantly share code, notes, and snippets.

@samueldr
Last active May 21, 2025 14:11
Show Gist options
  • Save samueldr/356e65374d452e4fd45314f818ae3545 to your computer and use it in GitHub Desktop.
Save samueldr/356e65374d452e4fd45314f818ae3545 to your computer and use it in GitHub Desktop.
Restarting a bluetooth USB device on NixOS.

These instructions will help you create a systemd service that will restart the a bluetooth USB dongle, working around the need to unplug/replug the device. As always, YMMV.

First, find out the Vendor ID and Product ID of your bluetooth dongle:

 $ nix-shell -p usbutils --run lsusb | grep -i bluetooth
Bus 003 Device 010: ID 1131:1004 Integrated System Solution Corp. Bluetooth Device

The Vendor ID and Product ID pair is found in the line, after ID. It is 1131:1004 for this particular device.

Then, in your configuration.nix, create a systemd service that will run

{
  # Forces a reset for specified bluetooth usb dongle.
  systemd.services.fix-generic-usb-bluetooth-dongle = {
    description = "Fixes for generic USB bluetooth dongle.";
    wantedBy = [ "post-resume.target" ];
    after = [ "post-resume.target" ];
    script = builtins.readFile ./scripts/hack.usb.reset;
    scriptArgs = "1131:1004"; # Vendor ID and Product ID here
    serviceConfig.Type = "oneshot";
  };
}

The script referenced in the script attribute is attached to this gist.

#!/usr/bin/env bash
#
# Resets the specified bluetooth USB adapter.
# Restarts bluetooth service.
#
# This is to work around an issue where after suspend, the bluetooth adapter is
# not working and needed to be unplugged and re-plugged.
#
# This inelegant script basically automates this.
#
set -e
set -u
PS4=" $ "
#set -x
#
# Values can be found using `lsusb`:
#
# Bus xxx Device yyy: ID VVVV:PPPP Integrated System Solution Corp. Bluetooth Device
# ^ ^
# ID_VENDOR-/ /
# ID_PRODUCT-/
#
ID_VENDOR="${1/:*/}"
ID_PRODUCT="${1/*:/}"
_reset_paths() {
for p in "$@"; do
echo 0 > "$p"/authorized
sleep 1
echo 1 > "$p"/authorized
done
}
#
# Main function for the script.
#
main() {
echo "Resetting USB bluetooth devices."
# Not strictly needed, but stops bluetooth.
# It will, in any way, be started at the end.
systemctl stop bluetooth
sleep 4
# Using a function allows use of local and declare.
local p
local found_vnd
local found_prd
declare -a paths
# For all usb devices,
for p in /sys/bus/usb/devices/*; do
# Try to check vendor/product IDs
found_vnd="$(cat "$p/idVendor" 2>/dev/null)" || :
found_prd="$(cat "$p/idProduct" 2>/dev/null)" || :
# When both match
if [[ "$found_vnd" == "$ID_VENDOR" && "$found_prd" == "$ID_PRODUCT" ]]; then
# Add to valid paths
paths+=("$p")
fi
done
# Reset all paths
_reset_paths "${paths[@]}"
sleep 1
# Twice for good luck
_reset_paths "${paths[@]}"
# Waits for everything to settle down
sleep 2
# Restarts bluetooth.
systemctl restart bluetooth
echo "Done resetting USB bluetooth devices."
}
# Calls up main.
main "$@"
@samueldr
Copy link
Author

The script IS dirty.

The script as of right now seems to work for every suspend/resume cycle I have tried.

No, I have not optimized it in full. I am probably doing dumb stuff.

@therealpxc
Copy link

therealpxc commented Dec 27, 2017

I experienced this issue (bluetooth becoming unaccessible upon resume from suspend) on a ThinkPad 25 (a Lenovo ThinkPad similar to the T470).

On my system, no device showed up in the list produced by the provided command, although I can confirm that modprobe -r btusb disables my bluetooth device. Before I dug in to determine which USB device was my bluetooth one, I did some manual experimentation which revealed that blocking and unblocking the USB device via rfkill works to solve this issue. According to this comment, doing has the same effect as this script, namely to simulate unplugging and replugging the USB bluetooth device. I liked the simplicity of the rfkill variation on this solution, so I've incorporated it into my NixOS config as follows:

powerManagement.resumeScript = ''
  ${pkgs.rfkill}/bin/rfkill block bluetooth
  ${pkgs.rfkill}/bin/rfkill unblock bluetooth
'';

(Note that the above will work even if powerManagement.enable = false; is set, making it compatible with the choice to use tlp for power management— despite what intuition might naïvely suggest.)

This is a blunter instrument than your solution, but I think it may be useful to share nonetheless, since it is perhaps a bit more newbie-friendly and generic.

@donn
Copy link

donn commented Mar 23, 2023

So funnily enough, this script helped me fix not a generic Bluetooth USB dongle, but my computer's Intel Corp. Wireless-AC 3168 Bluetooth. Yep.

Will definitely keep this script on hand.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment