Posts: 3
Joined: Sat Jan 20, 2018 3:12 pm

Bluetooth Pairing Headless Pi Zero W

Sat Jan 20, 2018 3:30 pm

I'm working on a project where I need to pair my Pi Zero W to my Linux laptop. The Pi is a headless embedded device. While I can ssh into it if needed, its primary method of communicating to the laptop in normal operation is via Bluetooth. I wanted a script to run on the Pi at power up that ensures it is paired. I realize that once it is paired, it should stay paired, but stuff always happens. ;) Getting this to work was a real pain, and a lot more trial and error than I would have expected. At this point I feel that this is a robust bash shell script, and I thought I'd share it here to help others who may be trying to do something similar. This pairs consistently when run from cron on my Pi Zero W running Raspbian GNU/Linux 9.1 (stretch), bluetoothctl Version 5.43, and empty version 0.6.20b. I'm also running a simple script on the laptop that executes the following bluetoothctl commands to ensure it is in a pairable state:
  • power on
  • agent on
  • default-agent
  • pairable on
  • discoverable on
If anyone has any additional suggestions feel free to add.

Code: Select all

# Gene Weber - February 3rd 2018
# Uses bluetoothctl and empty-expect to check for and establish bluetooth device pairing.
# sudo apt install empty-expect
# Add the line: @reboot /home/pi/ to the cron system daemon using
# crontab -u pi -e
# This script can also be run on the Linux PC to be paired by changing only the Device_ID.
# "paired-devices" and "discoverable on" do not work using empty on Pi Zero W in cron,
# so this script works around these issues.

# Bluetooth ID of device to be paired.

rm empty.log
rm paired.log

# Delay until bluetooth service is running after power up.
until [ $RETURN_STATUS -eq 0 ]
  sleep 1
  sudo service bluetooth status | grep 'Status: "Running"' >empty.log

while [ $PAIRED -ne 0 ]

  echo -e "This is pairing attempt "$PASS".\n" >paired.log
  # Write list of paired devices to log file.
  bluetoothctl << EOF 2>&1 >>paired.log
  # Check if device ID is in the list of paired devices.
  grep ^"$PAIR_STRING" paired.log >>empty.log
  # If device ID isn't paired, set controller to proper state and pair devices.
  if [ $PAIRED -ne 0 ]
    # Fork a bluetoothctl process. Capture PID and log.
    # and BTC.out are fifos for inter-process communication.
    empty -f -i -o BTC.out -p -L empty.log bluetoothctl
    # Allow some start up time for bluetoothctl
    sleep 2
    # Send power on command.
    empty -s -o 'power on\n'
    # Watch for return of "Changing power on succeeded", then send agent on command.
    empty -w -i BTC.out -o succeeded 'agent on\n'
    # Watch for return of "Agent registered", then send default-agent command.
    empty -w -i BTC.out -o registered 'default-agent\n'
    # Watch for return of "Default agent request successful", then send pairable on command.
    empty -w -i BTC.out -o successful 'pairable on\n'
    # Watch for return of "Changing pairable on succeeded", then trust Device.
    empty -w -i BTC.out -o succeeded 'trust '$DEVICE_ID'\n'
    # Watch for return of "trust succeeded" or "not available", then attempt pairing.
    # If Device was not availble to trust, it won't be available to pair, but that's OK.
    empty -w -i BTC.out -o succeeded 'pair '$DEVICE_ID'\n' available 'pair '$DEVICE_ID'\n'
    # Watch for "Pairing successful", "not available" or "org.bluez.Error.ConnectionAttemptFailed".
    # Start scanning of not available, else exit.
    empty -w -i BTC.out -o successful 'exit\n'  available 'scan on\n' ConnectionAttemptFailed 'exit\n'
    # Allow time for exit and PID removal.
    sleep 2
    # If PID still exists then Device was not availble and scan was started.
    if [ -f "" ]
      # Allow up to a 3 minutes to find the desired Device ID, turn scan off if found.
      empty -t 180 -w -i BTC.out -o $DEVICE_ID 'scan off\n'
      # Allow time after device is found before attempting next command.
      sleep 3

      # Watch for "Discovery stopped", then trust Device.
      empty -w -i BTC.out -o stopped 'trust '$DEVICE_ID'\n'
      # Watch for "trust succeeded", then attempt pairing.
      empty -w -i BTC.out -o succeeded 'pair '$DEVICE_ID'\n'
      # Watch for "Pairing successful", "not available" or "org.bluez.Error.ConnectionAttemptFailed".
      # Then send exit command.
      empty -w -i BTC.out -o successful 'exit\n' Failed 'exit\n' ConnectionAttemptFailed 'exit\n'
  # Ensure that the empty process gets terminated if script fails for some reason.
  sleep 2
  if [ -f "" ]
    empty -k `cat ./`
    echo -e "\nPairing script failed.\n" >> empty.log
  # Allow 5 tries to pair before giving up
  PASS=$((PASS + 1))
  if [ $PASS -eq 6 ]

# Make bluetooth discoverable in case the laptop needs to do the pairing.
bluetoothctl << EOF2 2>&1 >>paired.log
discoverable on
Last edited by geenweb on Sat Feb 03, 2018 4:51 pm, edited 1 time in total.

Posts: 3
Joined: Sat Jan 20, 2018 3:12 pm

Re: Bluetooth Pairing Headless Pi Zero W

Sun Jan 21, 2018 7:32 pm

Note that in this process I discovered that a bluetoothctl commands return "Invalid command" when executed with empty when the script is launched from nohup, cron, or /etc/rc.local. They work fine if the script is launched from the command line. This happens with the commands: "devices", "paired-devices", and "discoverable on". It is 100% repeatable. These errors do not occur if the script is run nohup on my laptop. The script simply works around these issues.

Posts: 3
Joined: Sat Jan 20, 2018 3:12 pm

Re: Bluetooth Pairing Headless Pi Zero W

Sat Feb 03, 2018 4:53 pm

The Bluetooth pairing script and my previous posts have been updated.

Return to “Networking and servers”