Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

bluetoothctl commands through python on Pi

I am running a sequence of Bluetoothctl commands on the terminal each time before I want to run a python script on my pi. I want to connect to a BLE Device automatically from the pi without any pairing confirmation or user interaction. Here are the commands I have to type each time I reboot the pi before I run another python script(the script will keep running for days after that until I stop or reboot the pi):

$sudo bluetoothctl
[Bluetooth]power on
[Bluetooth]discoverable on
[Bluetooth]pairable on
[Bluetooth]agent NoInputNoOutput
[Bluetooth]default-agent 

I want to automate this process. So, I tried to use the bluetoothctl wrapper and modified it, but doesn't seem to work. No errors either.

import time
import pexpect
import subprocess
import sys
import re

class BluetoothctlError(Exception):
    """This exception is raised, when bluetoothctl fails to start."""
    pass


class Bluetoothctl:
    def __init__(self):
        out = subprocess.check_output("rfkill unblock bluetooth", shell = True)
        self.child = pexpect.spawn("bluetoothctl", echo = False)
        print("bluetoothctl")

    def get_output(self, command, pause = 0):
        """Run a command in bluetoothctl prompt, return output as a list of lines."""
        self.child.send(command + "\n")
        time.sleep(pause)
        start_failed = self.child.expect(["bluetooth", pexpect.EOF])

        if start_failed:
            raise BluetoothctlError("Bluetoothctl failed after running " + command)

        return self.child.before.split(b"\r\n")

    def make_discoverable(self):
        """Make device discoverable."""
        try:
            out = self.get_output("discoverable on")
            print("discoverable on")
        except BluetoothctlError as e:
            print(e)
            return None


    def power_on(self):
        """Start agent"""
        try:
            out = self.get_output("power on")
            print("power on")
        except BluetoothctlError as e:
            print(e)
            return None


    def pairable_on(self):
        """Start agent"""
        try:
            out = self.get_output("pairable on")
            print("pairable on")
        except BluetoothctlError as e:
            print(e)
            return None

    def agent_noinputnooutput(self):
        """Start agent"""
        try:
            out = self.get_output("agent NoInputNoOutput")
            print("agent Registered Successfully")
        except BluetoothctlError as e:
            print(e)
            return None

    def default_agent(self):
        """Start default agent"""
        try:
            out = self.get_output("default-agent")
            print("set as default agent")
        except BluetoothctlError as e:
            print(e)
            return None

if __name__ == "__main__":
    print("Init bluetooth...")
    bl = Bluetoothctl()
    bl.power_on()
    bl.make_discoverable()
    bl.pairable_on()
    bl.agent_noinputnooutput()
    bl.default_agent()
like image 493
rohit Avatar asked Oct 23 '25 14:10

rohit


1 Answers

I wrote a python3 script to auto-connect my gamepads on my game cabinet. You have to run it for each device you want to connect, but no user interaction is needed. It uses the expect python module. I found it a little easier to use than expect/tcl scripts. If python can't find pexpect, you would need to install python3-pexpect.

sudo apt install python3-pexpect

You'll want to change the mylist list variable to search for the MACs that match the first 3 bytes (the vendor part) of your bluetooth devices. So, for example, if the first 3 bytes of the MACs on your devices start with AA:BB:CC:, then change the EF\:17\:D8\: part to AA\:BB\:CC\:

You can add as many devices you want to scan for in the mylist variable. My example searches for two different vendors, one starting with EF\:17\:D8\:, and one starting with 16\:04\:18\: The script will reject all other bluetooth devices that may be transmitting, and only connect the gamepad MACs you've configured in the mylist variable.

mylist = ['E4\:17\:D8\:[0-9A-F].[:][0-9A-F].[:][0-9A-F].', '16\:04\:18\:[0-9A-F].[:][0-9A-F].[:][0-9A-F].',pexpect.EOF]

Here is the python3 script:

#!/usr/bin/python3
import os,sys,time,pexpect

def findaddress():
  address=''
  p = pexpect.spawn('hcitool scan', encoding='utf-8')
  p.logfile_read = sys.stdout
  mylist = ['E4\:17\:D8\:[0-9A-F].[:][0-9A-F].[:][0-9A-F].', '16\:04\:18\:[0-9A-F].[:][0-9A-F].[:][0-9A-F].',pexpect.EOF]
  p.expect(mylist)
  address=p.after
  if address==pexpect.EOF:
    return ''
  else:
    return address

def setbt(address):
  response=''
  p = pexpect.spawn('bluetoothctl', encoding='utf-8')
  p.logfile_read = sys.stdout
  p.expect('#')
  p.sendline("remove "+address)
  p.expect("#")
  p.sendline("scan on")

  mylist = ["Discovery started","Failed to start discovery","Device "+address+" not available","Failed to connect","Connection successful"]
  while response != "Connection successful":
    p.expect(mylist)
    response=p.after
    p.sendline("connect "+address)
    time.sleep(1)
  p.sendline("quit")
  p.close()
  #time.sleep(1)
  return


address='' 
while address=='':
  address=findaddress()
  time.sleep(1)
  
print (address," found")
setbt(address)

I wrote another python3 script that wraps the entire process in a Vte and shows the process as it is happening, and lets you to exit it, if needed. If you want to see that, just let me know.

like image 164
Ken H Avatar answered Oct 26 '25 04:10

Ken H