I'm working on a setup where I want to connect a RaspberryPi-3 to another machine. Most of the time it will be a Windows machine. On the Pi I want to forward all Keyboard strokes via Bluetooth to the other (Windows) machine. Therefore I did some research, someone called Liam wrote a Python script a few years ago, which apparently worked fine with BlueZ 4. So I decided to give it a shot on BlueZ 5. No luck so far. Well, I thought, ok, lets downgrade the version. So I compiled it from source, version 4.10. No luck. The raspberry didn't even know it had a bluetooth adapter installed, possibly there was something wrong with a few drivers. So then I started to get into BlueZ 5, at first it was easy. But actually I am struggling now, to get the sdp_Record.xml registered. As well as a stable connection. So first of all the reference file with BlueZ 4 implementation: BlueZ4.py: #!/usr/bin/python2.7 # # PiTooth allows the Raspberry Pi to act as a Bluetooth keyboard, and relays # keypresses from a USB keyboard to a Bluetooth client. Written by Liam Fraser # for a Linux User & Developer tutorial. # import os # Used to call external commands import sys # Used to exit the script import bluetooth from bluetooth import * import dbus # Used to set up the SDP record import time # Used for pausing the process import evdev # Used to get input from the keyboard from evdev import * import keymap # Used to map evdev input to hid keycodes class Bluetooth: HOST = 0 # BT Mac address PORT = 1 # Bluetooth Port Number # Define the ports we'll use P_CTRL = 17 P_INTR = 19 def __init__(self): # Set the device class to a keyboard and set the name os.system("hciconfig hci0 class 0x002540") os.system("hciconfig hci0 name Raspberry\ Pi") # Make device discoverable os.system("hciconfig hci0 piscan") # Define our two server sockets for communication self.scontrol = BluetoothSocket(L2CAP) self.sinterrupt = BluetoothSocket(L2CAP) # Bind these sockets to a port self.scontrol.bind(("", Bluetooth.P_CTRL)) self.sinterrupt.bind(("", Bluetooth.P_INTR)) # Set up dbus for advertising the service record self.bus = dbus.SystemBus() try: self.manager = dbus.Interface(self.bus.get_object("org.bluez", "/"), "org.bluez.Manager") adapter_path = self.manager.DefaultAdapter() self.service = dbus.Interface(self.bus.get_object("org.bluez", adapter_path), "org.bluez.Service") except: sys.exit("[Bluetooth - L.50] Could not configure bluetooth. Is bluetoothd started?") # Read the service record from file try: fh = open(sys.path[0] + "/sdp_record.xml", "r") except: sys.exit("[Bluetooth - L.56] Could not open the sdp record. Exiting...") self.service_record = fh.read() fh.close() def listen(self): # Advertise our service record self.service_handle = self.service.AddRecord(self.service_record) print "[Bluetooth - L.63] Service record added" # Start listening on the server sockets self.scontrol.listen(1) # Limit of 1 connection self.sinterrupt.listen(1) print "[Bluetooth - L.68] Waiting for a connection" self.ccontrol, self.cinfo = self.scontrol.accept() print "[Bluetooth - L.70] Got a connection on the control channel from " + self.cinfo[Bluetooth.HOST] self.cinterrupt, self.cinfo = self.sinterrupt.accept() print "[Bluetooth - L.72] Got a connection on the interrupt channel from " + self.cinfo[Bluetooth.HOST] def send_input(self, ir): # Convert the hex array to a string hex_str = "" for element in ir: if type(element) is list: # This is our bit array - convert it to a single byte represented # as a char bin_str = "" for bit in element: bin_str += str(bit) hex_str += chr(int(bin_str, 2)) else: # This is a hex value - we can convert it straight to a char hex_str += chr(element) # Send an input report self.cinterrupt.send(hex_str) class Keyboard(): def __init__(self): # The structure for an bt keyboard input report (size is 10 bytes) self.state = [ 0xA1, # This is an input report 0x01, # Usage report = Keyboard # Bit array for Modifier keys (D7 being the first element, D0 being last) [0, # Right GUI - (usually the Windows key) 0, # Right ALT 0, # Right Shift 0, # Right Control 0, # Left GUI - (again, usually the Windows key) 0, # Left ALT 0, # Left Shift 0], # Left Control 0x00, # Vendor reserved 0x00, # Rest is space for 6 keys 0x00, 0x00, 0x00, 0x00, 0x00 ] # Keep trying to get a keyboard have_dev = False while have_dev == False: try: # Try and get a keyboard - should always be event0 as we're only # plugging one thing in self.dev = InputDevice("/dev/input/event0") have_dev = True except OSError: print "[Keyboard - L.124] - Keyboard not found, waiting 3 seconds and retrying" time.sleep(3) print "[Keyboard - L.127]Found a keyboard" def change_state(self, event): evdev_code = ecodes.KEY[event.code] modkey_element = keymap.modkey(evdev_code) if modkey_element > 0: # Need to set one of the modifier bits if self.state[2][modkey_element] == 0: self.state[2][modkey_element] = 1 else: self.state[2][modkey_element] = 0 else: # Get the hex keycode of the key hex_key = keymap.convert(ecodes.KEY[event.code]) # Loop through elements 4 to 9 of the input report structure for i in range (4, 10): if self.state == hex_key and event.value == 0: # Code is 0 so we need to depress it self.state = 0x00 elif self.state == 0x00 and event.value == 1: # If the current space is empty and the key is being pressed self.state = hex_key break def event_loop(self, bt): for event in self.dev.read_loop(): # Only bother if we a key and it's an up or down event if event.type == ecodes.EV_KEY and event.value < 2: self.change_state(event) bt.send_input(self.state) if __name__ == "__main__": # We can only run as root if not os.geteuid() == 0: sys.exit("[FATAL] - Only root can run this script (sudo?)") bt = Bluetooth() bt.listen() kb = Keyboard() kb.event_loop(bt) What I did so far was trying to migrate this old BlueZ 4 code to version 5. My implementation so far: #!/usr/bin/python2.7 import os import sys import bluetooth from bluetooth import * import dbus import time import evdev from evdev import * import keymap class Bluetooth: HOST = "<REMOTEMACHINEMAC>" #<PIMAC> #HOST = 0 PORT = 1 # Define the ports we'll use P_CTRL = 17 P_INTR = 19 def __init__(self): os.system("hciconfig hci0 class 0x002540") os.system("hciconfig hci0 name Raspberry\ Pi") # Define our two server sockets for communication self.scontrol = BluetoothSocket(L2CAP) self.sinterrupt = BluetoothSocket(L2CAP) # Bind these sockets to a port self.scontrol.bind(("", Bluetooth.P_CTRL)) self.sinterrupt.bind(("", Bluetooth.P_INTR)) # Set up dbus for advertising the service record self.bus = dbus.SystemBus() # Set up dbus for advertising the service record try: self.objManager = dbus.Interface(self.bus.get_object("org.bluez", "/"), "org.freedesktop.DBus.ObjectManager") #print self.manager.GetManagedObjects()["/org/bluez/hci0"] self.manager = dbus.Interface(self.bus.get_object("org.bluez", "/org/bluez"), "org.bluez.ProfileManager1") self.hci_props = dbus.Interface(self.bus.get_object("org.bluez", "/org/bluez/hci0"), "org.freedesktop.DBus.Properties") except: print sys.exc_info() sys.exit("[FATAL] Could not set up Bluez5") # Read the service record from file try: fh = open(sys.path[0] + "/sdp_record.xml", "r") except: sys.exit("[Bluetooth - L.56] Could not open the sdp record. Exiting...") self.service_record = fh.read() fh.close() try: opts = { "AutoConnect": 1, "ServiceRecord": self.service_record } uuidarray = self.hci_props.Get("org.bluez.Adapter1", "UUIDs") for uuids in uuidarray: try: self.manager.RegisterProfile("/org/bluez/hci0", uuids, opts) except: print uuids print "Service Record saved!" except: print "Service Records saved. Probably already exists" #print sys.exc_info() #sys.exit("Error updating service record") print "Update class again" #os.system("hciconfig hci0 class 0x002540") #os.system("hciconfig hci0 name Raspberry\ Pi") def listen(self): # Advertise our service record #self.service_handle = self.service.AddRecord(self.service_record) #print "[Bluetooth - L.63] Service record added" # Start listening on the server sockets self.scontrol.listen(1) # Limit of 1 connection self.sinterrupt.listen(1) print "[Bluetooth - L.68] Waiting for a connection" self.ccontrol, self.cinfo = self.scontrol.accept() print "[Bluetooth - L.70] Got a connection on the control channel from " + self.cinfo[Bluetooth.HOST] self.cinterrupt, self.cinfo = self.sinterrupt.accept() print "[Bluetooth - L.72] Got a connection on the interrupt channel from " + self.cinfo[Bluetooth.HOST] def python_to_data(self, data): if isinstance(data, str): data = dbus.String(data) elif isinstance(data, bool): data = dbus.Boolean(data) elif isinstance(data, int): data = dbus.Int64(data) elif isinstance(data, float): data = dbus.Double(data) elif isinstance(data, list): data = dbus.Array([self.python_to_data(value) for value in data], signature='v') elif isinstance(data, dict): data = dbus.Dictionary(data, signature='sv') for key in data.keys(): data[key] = self.python_to_data(data[key]) return data class Keyboard(): def __init__(self): # The structure for an bt keyboard input report (size is 10 bytes) self.state = [ 0xA1, # This is an input report 0x01, # Usage report = Keyboard # Bit array for Modifier keys (D7 being the first element, D0 being last) [0, # Right GUI - (usually the Windows key) 0, # Right ALT 0, # Right Shift 0, # Right Control 0, # Left GUI - (again, usually the Windows key) 0, # Left ALT 0, # Left Shift 0], # Left Control 0x00, # Vendor reserved 0x00, # Rest is space for 6 keys 0x00, 0x00, 0x00, 0x00, 0x00 ] # Keep trying to get a keyboard have_dev = False while have_dev == False: try: # Try and get a keyboard - should always be event0 as we're only # plugging one thing in self.dev = InputDevice("/dev/input/event0") have_dev = True except OSError: print "[Keyboard - L.124] - Keyboard not found, waiting 3 seconds and retrying" time.sleep(3) print "[Keyboard - L.127]Found a keyboard" def change_state(self, event): evdev_code = ecodes.KEY[event.code] modkey_element = keymap.modkey(evdev_code) if modkey_element > 0: # Need to set one of the modifier bits if self.state[2][modkey_element] == 0: self.state[2][modkey_element] = 1 else: self.state[2][modkey_element] = 0 else: # Get the hex keycode of the key hex_key = keymap.convert(ecodes.KEY[event.code]) # Loop through elements 4 to 9 of the input report structure for i in range (4, 10): if self.state == hex_key and event.value == 0: # Code is 0 so we need to depress it self.state = 0x00 elif self.state == 0x00 and event.value == 1: # If the current space is empty and the key is being pressed self.state = hex_key break def event_loop(self, bt): for event in self.dev.read_loop(): # Only bother if we a key and it's an up or down event if event.type == ecodes.EV_KEY and event.value < 2: self.change_state(event) bt.send_input(self.state) if __name__ == "__main__": # We can only run as root if not os.geteuid() == 0: sys.exit("[FATAL] - Only root can run this script (sudo?)") bt = Bluetooth() bt.listen() kb = Keyboard() kb.event_loop(bt) The thing is, I'm wondering about now are: Is the HOST variable correct? Is even the PORT correct? Did I miss out an important step? Because, my print out of the Python script is: [Bluetooth - L.68] Waiting for a connection as well as, the bluetooth connection drops as soons as it "connects" with the remote machine. What I also noticed is, that I think the SPD_Record.xml won't get set correctly. SDP_Record.xml for reference: <?xml version="1.0" encoding="UTF-8" ?> <record> <attribute id="0x0001"> <sequence> <uuid value="0x1124" /> </sequence> </attribute> <attribute id="0x0004"> <sequence> <sequence> <uuid value="0x0100" /> <uint16 value="0x0011" /> </sequence> <sequence> <uuid value="0x0011" /> </sequence> </sequence> </attribute> <attribute id="0x0005"> <sequence> <uuid value="0x1002" /> </sequence> </attribute> <attribute id="0x0006"> <sequence> <uint16 value="0x656e" /> <uint16 value="0x006a" /> <uint16 value="0x0100" /> </sequence> </attribute> <attribute id="0x0009"> <sequence> <sequence> <uuid value="0x1124" /> <uint16 value="0x0100" /> </sequence> </sequence> </attribute> <attribute id="0x000d"> <sequence> <sequence> <sequence> <uuid value="0x0100" /> <uint16 value="0x0013" /> </sequence> <sequence> <uuid value="0x0011" /> </sequence> </sequence> </sequence> </attribute> <attribute id="0x0100"> <text value="Raspberry Pi Virtual Keyboard" /> </attribute> <attribute id="0x0101"> <text value="USB > BT Keyboard" /> </attribute> <attribute id="0x0102"> <text value="Raspberry Pi" /> </attribute> <attribute id="0x0200"> <uint16 value="0x0100" /> </attribute> <attribute id="0x0201"> <uint16 value="0x0111" /> </attribute> <attribute id="0x0202"> <uint8 value="0x40" /> </attribute> <attribute id="0x0203"> <uint8 value="0x00" /> </attribute> <attribute id="0x0204"> <boolean value="true" /> </attribute> <attribute id="0x0205"> <boolean value="true" /> </attribute> <attribute id="0x0206"> <sequence> <sequence> <uint8 value="0x22" /> <text encoding="hex" value="05010906a101850175019508050719e029e715002501810295017508810395057501050819012905910295017503910395067508150026ff000507190029ff8100c0050c0901a1018503150025017501950b0a23020a21020ab10109b809b609cd09b509e209ea09e9093081029501750d8103c0" /> </sequence> </sequence> </attribute> <attribute id="0x0207"> <sequence> <sequence> <uint16 value="0x0409" /> <uint16 value="0x0100" /> </sequence> </sequence> </attribute> <attribute id="0x020b"> <uint16 value="0x0100" /> </attribute> <attribute id="0x020c"> <uint16 value="0x0c80" /> </attribute> <attribute id="0x020d"> <boolean value="false" /> </attribute> <attribute id="0x020e"> <boolean value="true" /> </attribute> <attribute id="0x020f"> <uint16 value="0x0640" /> </attribute> <attribute id="0x0210"> <uint16 value="0x0320" /> </attribute> </record> I'd be more than glad, if some can help or point me in the right direction, to get this working again. Thanks in advance for your help! Continue reading...