Saturday, April 16, 2016

Emulating a Bluetooth Keyboard with a Raspberry Pi and Python (Raspbian Jessie/Bluez 5 version)


A while ago I followed this article  to turn a Raspberry Pi into a Bluetooth HID Keyboard emulator.

I recently tried to repeat this process on  Raspbian Jessie and I found that I was unable to get it to work.  After a bit of research I discovered that Jessie includes an upgraded version of Bluez, the Linux Bluetooth stack, and that a lot of the instructions in the article were no longer valid.

 Here is my attempt to create an updated version of the emulator that will work with Bluez5 and Jessie.

(All credit to the original author of the Linux User Magazine article; I wouldn't have managed to get to the first step without following their instructions.)

Bluez 5 has substantially changed how the bluetooth daemon is configured, how SDP profiles are advertised and how Bluetooth pairing occurs. Therefore there are quite a few changes that need to be made to the process given in the original article and to the scripts.

I've also taken the liberty of introducing my own changes to the scripts to allow a more generic use than a simple keyboard forwarder - I've turned the Bluetooth keyboard emulation script into a DBUS service that will forward any keys it receives to the Bluetooth host. This simplifies the development of multiple clients of different types.


Here are the steps to get things up and running

Set up Raspberry Pi

Obtain a Pi running Raspbian Jessie. Ensure the Pi is running the latest version.

>sudo apt-get update
>sudo apt-get upgrade

Install Bluetooth

This step is the same process as for Step 03 of the original article.

> sudo apt-get install python-gobject bluez bluez-tools bluez-firmware python-bluez python-dev python-pip
> sudo pip install evdev

Bluetooth Daemon Configuration

This is where things start to diverge. Bluez 5 uses a different mechanism than Bluez 4 to configure plug-ins and SDP profiles.  It is no longer possible to disable plug-ins using /etc/bluetooth/main.conf

Instead, I found that the easiest thing to do was to kill the bluetooth daemon and run it in the foreground

 - Stop the background process

> sudo /etc/init.d/bluetooth stop

 - Open a dedicated terminal and tun the bluetooth daemon in the foreground

> sudo /usr/sbin/bluetoothd --nodetach --debug -p time


Note; This will disable all bluetooth plug-ins except the time plug-in.  I found this the easiest way to ensure that plug-ins were not interfering with the keyboard emulator. An improvement would be to identify and disable only the plug-ins that would conflict with the emulator code.

Download the code

Clone the yaptb git reposutory

> git clone https://github.com/yaptb/BlogCode.git


The code for the bluetooth keyboard emulator in is in the /btkkeyboard  directory. There are three subfolders

server - contains the  bluetooth keyboard emulator code
dbus - contains a DBUS system bus configuration for the btkserver
keyboard - contains a btkserver client application that sends local keystrokes to the emulator

Configure DBUS

My reworked version of the keyboard emulator sets itself up as a DBUS system bus server.  This allows client applications to use DBUS to send keystrokes to the emulator and allows the simple creation of multiple different types of clients.

For this to work, the DBUS system bus needs to be configured to add the btkserver API.  This is enabled by copying a configuration file into the /etc/dbus-1/system.d folder

> cd BlogCode/btkeyboard/dbus
> sudo cp org.yaptb.btkkbservice.conf /etc/dbus-1/system.d

hciconfig


Depending on your system, you may need to manually enable your bluetooth device.  The hciconfig utility can be used to control the bluetooth adapter.

  >sudo hciconfig hcio

 should list the status of your bluetooth adapter. If the status is UP and RUNNING then all is well, otherwise run the following command to enable it

 > sudo hciconfig hcio up



Edit the Emulator Server Code

The emulator server code (btkserver.py) needs to be updated with the Bluetooth Device address of your bluetooth adapter before it will work.  First, use hciconfig to find the address:

>sudo hciconfig hcio



Then, use a text editor to edit the code and update the constant MY_ADDRESS in btk_server.py
> cd BlogCode/btkeyboard/server
> leafpad btk_server.py &
Change the value of the constant MY_ADDRESS to be the same as the value returned from hciconfig and save the file.




Run Emulator Server

The bluetooth emulator is a python script that is a modfied version of the one in the original article.  The main changes I have made are to adapt to the Bluez5 profile mechanism for advertising the SDP record and to set up a DBUS api for interacting with the emulated keyboard.  I've also tweaked the SDP record a little.

Because the script interacts with the system DBUS, it needs to be run as root.

> cd BlogCode/btkeyboard/server
> sudo python btkserver.py

(Remember to have the bluetooth deamon running in a separate terminal window before doing this)

If all goes well, you should see something like the following:



Pairing


Before the keyboard can talk to a host, it needs to be paired.  This can be tricky and I've spent a lot of time trying to get this to work smoothly.  Through trial and error, here is the process I use (I am going to assume you are pairing with a Windows 10 host here)

1. Open (yet another) dedicated terminal window and run the bluetoothctl utility,

> sudo /usr/bin/bluetoothctl

2. Type the following commands, pressing enter after each

> agent on
> default-agent
> scan on
> discoverable on

(I am guessing that this puts the bluetooth device in page scan mode and sets up an agent for pairing. Probably time to RTFM to be honest).

You should see something like this:



3. Go to your host machine and look at the Bluetooth Settings. On Windows 10 this is achieved by

Start Menu -> Settings -> Devices -> Bluetooth

If all is going well, you should see the Raspberry Pi keyboard ready to pair:



Click the device and select pair.  You will see a dialog with a passcode. Don't click anything yet.



3. Go back to the bluetoothctl window on the Pi and wait a few seconds,  You should then be prompted to accept or reject the pairing request from the host:




Type yes


4. Quickly go back to the Windows host and click OK.  If everything has worked the keyboard should pair and Windows will start installing a keyboard driver for the emulator.  You should also see a connection message in the btkserver window.



If you get this fair your keyboard emulator is set up and ready to use!


Note: The latest version of rasbian Jessie includes a Bluetooth widget on the desktop.  This widget will attempt to 'hijack' the pairing process and will prompt you to complete  process through a dialog window.  Ignoring this message and using the process above seems to work. Once you have paired through the command line you can safely close this window.


Local Keyboard Mirroring


The code in the original article mirrored the local Pi keyboard events to the emulator.  I've adapted this code and used it to create a client for the btkserver to do the same thing.

After setting up the emulator, open another terminal window (that makes 4) and run the keyboard mirroring client

> cd BlogCode/btkeyboard/keyboard
> sudo python kb_client.py



After running this program, any keys typed on the Pi should be mirrored to the Windows host:




Timeouts and Disconnects

So far this works well, but the Windows Host seems to disconnect the keyboard after a period of inactivity,  Unfortunately this means that you often need to restart the emulator and remove and re-add and re-pair the device in Windows to get it to reconnect.

I am still working through this issue,  but things seem to improve if you switch the Raspberry Pi bluetooth device to local master mode.

> sudo hciconfig hcio lm master

Your mileage may vary on this.  Hopefully someone let me know how to fix this issue. I'll post a follow up if I work it out.


Update


I have written a follow-up to this post showing how to control the keyboard emulator from the Raspberry Pi GPIO bus  

53 comments:

  1. What you did seems to be just what I'm looking for.
    However, when I'm trying to connect my raspy to my android cell phone - I get everything up to the pairing, but no connection.

    Every time I try to connect them (usually via bluetoothctl), it connects and immediately disconnects.

    Any ideas why this might be?

    BTW, I think there's a typo in the bluetooth daemon stop line - shouldn't it be init.d ?
    Also, after you stopped the daemon, you never started it again - is the python script supposed to do that? I've had to start it up manually before starting the script, otherwise just getting errors.
    (Which might be an indication that something basic is wrong, of course.)

    I'm using an old Raspy 1 and a LG P880, unfortunately I currently have no other bluetooth device to test if it's the phone - and the phone is the main target anyway.

    ReplyDelete
    Replies
    1. Cyberman, have you made any progress figuring this out?

      I have the same problem you are describing.
      I am using an image of Raspbian Jessie with the release date of 2016-05-27 on the Raspberry Pi 3 Model B.

      After following this tutorial I am able to pair with the Pi, but there is no connection. On step 4 of Pairing I am unable to see a connection on the btk_server. Bluetoothctl shows that a connection happened but it is immediately disconnected. I have tried pairing with Windows 7, ChromeOS, and Android.

      I also needed to setup hci0. It can be done like this:
      sudo hciconfig hc0 up

      Delete
    2. With a Raspberry Pi 3 (and connecting to the pi-keyboard from a Debian laptop), I am likewise getting immediate disconnection after connection.

      connected: yes
      connected: no
      connected: yes
      connected: no

      I'll let you know if I figure out what's going on here. If you ever solved this, it would be great to know how.

      Delete
    3. Specifically, the error message which comes (on the pi side) with this instantaneous connect-disconnect problem is:
      """
      Connection failed -
      GDBus.Error.org.bluez.Error.NotAvailable: Operation currently not available. Try to connect manually.
      """

      Delete
  2. Thanks for the tutorial!

    I think "/usr/sbin/bluetoothd --nodetach --debug -p time" needs to be run as sudo.
    Also, don't we need to somehow start hci0? I tried "sudo hciconfig hci0 up" which seemed to work.

    Unfortunately, the bluetooth connection between the Pi and the client PCs keeps disconnecting almost immediately so I wasn't able to verify the keyboard emulation worked. I was able to once get Windows to install the keyboard driver, but the kb_client.py didn't seem to do anything after "starting event loop". I'm pretty much at a loss on what to do next so I'm hoping somebody can post their solution. I'll report back if I make progress.

    ReplyDelete
  3. Hey keef, thanks for the tutorial, I was walkway through doing this on my own, because I only found the outdated tutorial you linked at the top when I found this. Really helpful.

    After a lot of trouble I finally got my Raspi to pair and connect to my macbook pro. But for some reason the btw_server script does not recognize this, it stays on "Waiting for connections".
    Unfortunately I don't have a Windows host around to test if that's the cause, but it doesn't work with my iPad and iPhone as well (bluetoothctl says paired and connected, but btw_server doesn't)

    Any idea as to why that might be the case?

    thanks,
    Finn

    ReplyDelete
  4. Here the line for the Bluetooth Daemon Configuration (tested on Ubuntu)
    In the file /etc/systemd/system/dbus-org.bluez.service
    Replace the line ExecStart by:
    ExecStart=/usr/lib/bluetooth/bluetoothd -P input

    ReplyDelete
  5. This is way too late and way too embarrassing but I had no idea anyone was reading this let alone making comments, and I let the blog slide. Sorry for leaving this hanging.

    Connectivity issues are most likely going to be either a connection issue between the keyboard server code and bluez (via dbus) or due to protocol differences between the client and the server. Tweaking the sdp_record may help with those.

    I've got a Pi3 now and a few more bluetooth enabled devices, so I am going to go back through this with a Pi3 and see if any corrections needs to be made.

    ReplyDelete
  6. I think most of the above problems are caused because I forgot to mention that the btk_server.py file has a hard coded constant that needs to be updated with the BD address of your adapter! I've added a section for this and made a couple of the suggested corrections. Hope that helps.

    ReplyDelete
  7. Thank you! (This comment is typed via a Pi3.)

    Now that it's up and running, I'll attempt writing a client for it. I'll link you in if you're interested. This is exciting.

    ReplyDelete
    Replies
    1. Please do! I've also posted a follow up that shows how to hook up a GPIO pin to trigger the keyboard event. That might give you some ideas

      Delete
  8. Nice tutorial! I'm using this for a school project and I keep getting errors like "1.3"8 is not allowed to own the service "org.yaptb.btkservice" due to security policies in the configuration file. Thoughts?

    ReplyDelete
  9. This sounds like a D-Bus security issue. Make sure you are running the service as root using the sudo command. e.g. sudo python btkserver.py

    ReplyDelete
  10. Thanks for your great work. After a few hours, I could eventually get it running. I made some changes to the script and I may add some new features (e.g., mouse support).

    I don't see any license file in your repository. That would be great if you add a license file so others can contribute to your great work as well.

    By the way, is the any documentation/tutorial that describes the /dev/input/eventX format and the bluetooth HID output format?

    ReplyDelete
    Replies
    1. Thanks for the tip. I've added an MIT license so got for it.

      The Bluetooth sdp profile details are in the Bluetooth HUMAN INTERFACE DEVICE PROFILE 1.1 document from bluetooth.org.

      I borrowed the input code from the original article, but 'man evdev' would be my first point of call to find out more.



      Delete
    2. Hello geeks
      First of all thank you for informative tutorial. i got the working demo on raspberry pi zero w

      I'm trying to emulate mouse along with the keyboards that's why I have make the mouse_client.py file and add the mouse descriptor in code, I have also make the functions that send the mouse data after successfully detection of the mouse and that is as follow

      @dbus.service.method('org.yaptb.btkbservice', in_signature='yay')
      def send_mouse(self, buttons, rel_move):

      print("Received Mouse Input, sending it via Bluetooth")
      cmd_str = ""
      cmd_str += chr(0xA1)
      cmd_str += chr(0x02)
      cmd_str += chr(buttons)
      cmd_str += chr(rel_move[0])
      cmd_str += chr(rel_move[1])
      cmd_str += chr(rel_move[2])
      cmd_str += chr(0x00)
      cmd_str += chr(0x00)

      self.device.send_string(cmd_str)

      After some testing and i got the mouse detected and able to send the data from pi via Bluetooth, I also got the connections at host but I'm getting nothing (No cursor) movement at windows host

      Can you please help me here
      Thanks

      Delete
  11. Anyone tried or managed to get this method to work with pairing to an Apple TV? I can see the kb server on my Windows PC but the Apple TV is not showing it.

    I suspect Apple have something strict about what the bluetooth keyboard must look like that is preventing discovery but I'm not sure what.

    Cheers.

    ReplyDelete
  12. Hello
    I have a problem with running btk_server.py
    My error is
    "File "./btk_server.py", line 226, in
    myservice = BTKbService();
    File "./btk_server.py", line 198, in __init__
    self.device.listen();
    File "./btk_server.py", line 159, in listen
    self.scontrol.bind((self.MY_ADDRESS,self.P_CTRL))
    File "/usr/lib/python2.7/dist-packages/bluetooth/bluez.py", line 140, in bind
    return self._sock.bind (addrport)
    _bluetooth.error: (98, 'Addres already is in use')

    Please help me resolve problem

    ReplyDelete
    Replies
    1. Hi Tomek, I think that can happen if you eitehr a) don't disable your other bluetooth services or b) try and run multiple copies of the server at the same time. Have you verified that bluetoothd isn't running in the background?

      Delete
  13. I'm still having the connectivity issue. I can connect to my rpi from my iMac, but the btk_server never receives the connection. Any thought?

    ReplyDelete
    Replies
    1. First thing to check is that you've updated the python server script to use the address of your Bluetooth device. I missed this step from the original article and only added it in recently so you may have missed it.

      Delete
    2. Yeah, I got that in there. I'm wondering if it has something to do with the way MacOS connects since I'm connecting to my RPi3 from my iMac..

      Delete
    3. Could be, tbh I haven't had a chance to try this on a Mac yet. Has anyone else managed it? If you have paired the two devices successfully then you might want to try checking the debug output of bluetoothd for clues. See if any errors are being reported when the Mac initiates a connection. When I get a chance I'll try this on a Mac and post an update.

      Delete
  14. This comment has been removed by the author.

    ReplyDelete
  15. Hi , I did it and working like charm. But when run Serial Port profile(SPP) along with ' kb_client.py ' it not working. I think we need to enable a plugin to support SPP ? Can anyone identify which is to be enabled so that i can run both HID and SPP ?
    Thanks in Advance

    ReplyDelete
    Replies
    1. Yes, you probably need to enable one of the bluetoothd plugins for this to work, but I'm afraid I can't tell you which one. There doesn't seem to be a lot of doco out there. Trial and error might be your best bet. Sorry, keef

      Delete
  16. I had same problem of connecting and then disconnecting after a couple of seconds. I solved it by 'trusting' the Bluetooth adaptor on the PC I am connecting to:-

    $ sudo /usr/bin/bluetoothctl

    then type the following command (without the '>', pressing enter after:

    > devices

    this will list all the devices. Pick out the BD code of the device you are paired to, then use that to 'trust' the device (replacing the following with your own BD code).

    > trust MY:EX:AM:PL:E0:00

    That's it. If it is still not connected, then connect using:

    > connect MY:EX:AM:PL:E0:00

    For other commands in bluetoothctl look at

    > help






    ReplyDelete
  17. The above turned out not to be a complete fix. It worked initially, but when reconnecting, I got further errors ('transport endpoint not recognised' etc.) I fixed these by using the bluetoothctl program, removing the device that had originally been paired (see bluetoothctl help), and then pairing again using the pair command in bluetoothctl. I also checked the BD Address using bluetoothctl. The only issue now is that when restarting btkbserver.py, the BT keyboard will not automatically reconnect to a Win10 PC. The workaround is to turn BT off and on again on the PC. It then connects and works well. I suspect this may be because of an issue with incomplete installation of the Bluetooth device in Win10 (as indicated in device properties, events in Win10), perhaps because some installation information is missing (MAC address, and possibly manufacturer, model might be needed).

    A simple modification of the kb_client.py allows keys to be sent from a python script as well as connected USB keyboard. (I use this to relay commands from an IR Remote control to the PC using bt keyboard).

    ReplyDelete
  18. i would like to send pre-defined string using raspberry pi bluetooth emulated keyboard. can u provide me with the code and iam not a python programer. thanks

    ReplyDelete
    Replies
    1. Let me have look a this. Might take a few days.

      Delete
    2. Ok done. Check out the latest post.
      http://yetanotherpointlesstechblog.blogspot.com.au/2017/08/updated-bluetooth-keyboard-client-code.html

      Delete
    3. thank you very much. you are awesome

      Delete
  19. Thanks a lot for this project !! very useful one! Is there a way to emit signals to the dbus when a pc is connected to the btk server?

    ReplyDelete
    Replies
    1. Its certainly seems to be feasible. You'd need to modify the listen() method in btk_server.py file to emit a dbus signal when a connection is established. See Emitting Signals here https://dbus.freedesktop.org/doc/dbus-python/doc/tutorial.html#emitting-signals-with-dbus-service-signal

      Delete
  20. This comment has been removed by the author.

    ReplyDelete
  21. Thanks for the great project. It's helping me a lot.
    Do you know if it's possible to make the device that is running the server as audio sink in another device at the same time? I tried but the device doesnt appear on the sinks list in the other device.
    Resuming: I have a main device and I want another one to emulate a keyboard and at the same time be setted as audio sink of the main one.
    Thanks in advance.

    ReplyDelete
    Replies
    1. I'm not sure what you mean by 'audio sink', so bear with me here. If you want the Pi running the bluetooth keyboard to act as a bluetooth speaker/headset, then this should be possible as long as enable the right plugin when you run the bluetoothd deamon. If you want to use a different method (say Apple Air Play - see https://www.lifehacker.com.au/?r=US ) to sync the audio then this would be independent of bluetooth and shouldn't be affected at all by the keyboard server. Hope that helps, keef

      Delete
    2. Yes, I wanted it to act as a bluetooth speaker. I didn't disable all bluetooth plug-ins on daemon configuration and it worked. Thanks.

      Delete
  22. 4. Quickly go back to the Windows host and click OK. If everything has worked the keyboard should pair and Windows will start installing a keyboard driver for the emulator. You should also see a connection message in the btkserver window.

    --> hi, keyboard driver is auto installing on window or manual, I think window will pop-up a warning for accept receive driver file!

    ReplyDelete
  23. I had try step by step to setup and connected to the window host, but cannot send the char that i typed on kb_client

    ReplyDelete
  24. Hi, Can we set to Automatically accepting Bluetooth in RPI?

    ReplyDelete
  25. What a fantastic article! Thank you for this. Is there perhaps a way to run all of this without having to actually open terminals and view them? I'd like to do this, but be able to essentially plug the Pi in, connect the Bluetooth via the computer, and start typing. Can all of these functions be put into startup?

    ReplyDelete
  26. This comment has been removed by the author.

    ReplyDelete
  27. Thanks for this post, I really appriciate. I have read posts, all are in working condition. and I really like your writing style. Keep it up like.
    Buy Inverter Battery Online

    ReplyDelete
  28. This comment has been removed by the author.

    ReplyDelete
  29. It seems like btk_server.py is dependent on GTK. I think I got it running by installing GTK2.

    sudo apt install python-gtk2

    ReplyDelete

  30. Thanks for the nice post. I used this as the basis for building a Raspberry Pi based Macro keyboard. The front end is written in C++, displays via OpenGL, and works quite nicely with the server.py backend. I extended to server to support mouse as well, everything works well except that when I add mouse wheel support to the descriptor, Windows 10 will fail to load the device driver (it does pair and work perfectly with Linux). Removing mouse wheel support from the descriptor and it all works (except the mouse wheel doesn't).

    I did setup several scripts to make pairing pretty automatic after the device is paired once. The scripts basically just kill server.py, stop bluetooth, start bluetooth, and start the server. Then I just toggle bluetooth on Windows or Linux and it is paired.

    ReplyDelete
    Replies
    1. Sounds like what i'm about to setup.
      Is it possible to share your code or parts? Would save me a lot of trial and error. Thanks.

      Delete
  31. Hi @keef,

    Thanks for the great post. It has been very helpful.

    I ported it across to Python 3 and removed the use of hciconfig because that has been deprecated by BlueZ.

    If you would like to take a look at what I did then here is the link to a Gist.
    https://gist.github.com/ukBaz/a47e71e7b87fbc851b27cde7d1c0fcf0#file-btk_server-py

    ReplyDelete
  32. When the device disconnects either due to a link failure or it being out of range, I cant connect again. I need to manually restart the emulator and remove and re-add and re-pair the device.

    Any solution to this?

    ReplyDelete
  33. Thank you. This was a great post. I was looking for a software version of HID Relay. It was nice to fine it in a ready to run post.

    ReplyDelete
  34. how can i send my push button data to android device from terminal window. any solution for this ?

    ReplyDelete
  35. Is there a script that will run this when the pi boots?

    ReplyDelete