Skip to content

Instantly share code, notes, and snippets.

@samueldr samueldr/_description.md Secret
Last active Dec 27, 2017

Embed
What would you like to do?
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

This comment has been minimized.

Copy link
Owner Author

commented Oct 14, 2017

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

This comment has been minimized.

Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.