In this post, I will create BLE GATT server on Raspberry Pi 3 using BlueZ dbus interface with Python. I will reuse BlueZ example code as much as possible. As an example of GATT service, I’ll create UART service (a.k.a. Nordic UART Service/NUS [1] ), so that I can test it with Nordic’s smartphone app [2].
Assumptions
Steps
1. BlueZ Update
Raspbian Stretch comes with BlueZ 5.43. BlueZ’s Advertising Manager has been officially supported from 5.48. So, let’s update BlueZ by following this post.
2. UART Service Implementation
I’ll use “example-advertisement” and “example-gatt-server” from the downloaded source code in Step 1. Some of the classes and functions will be reused.
2-1. First, let’s create a working directory.
mkdir ble-uart-peripheral && cd ble-uart-peripheral
2-2. Copy the example code with new names so that they can be imported as modules.
cp ~/bluez-5.50/test/example-advertisement ./example_advertisement.py
cp ~/bluez-5.50/test/example-gatt-server ./example_gatt_server.py
Note: The file name of python module must have the suffix “.py” and should not have “-” (hyphen) in it.
2-3. Create a new file (e.g. “uart_peripheral.py”). I’ll reuse ‘Advertisement’ class, ‘Service’ class, ‘Characteristic’ class, and callback functions from the example code. Also, combine the both main() functions from the example code into one and modify the sub-classes of Advertisement, Application, Service, and Characteristic to realize the UART service.
import sys
import dbus, dbus.mainloop.glib
from gi.repository import GLib
from example_advertisement import Advertisement
from example_advertisement import register_ad_cb, register_ad_error_cb
from example_gatt_server import Service, Characteristic
from example_gatt_server import register_app_cb, register_app_error_cb
BLUEZ_SERVICE_NAME = 'org.bluez'
DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
GATT_MANAGER_IFACE = 'org.bluez.GattManager1'
GATT_CHRC_IFACE = 'org.bluez.GattCharacteristic1'
UART_SERVICE_UUID = '6e400001-b5a3-f393-e0a9-e50e24dcca9e'
UART_RX_CHARACTERISTIC_UUID = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'
UART_TX_CHARACTERISTIC_UUID = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'
LOCAL_NAME = 'rpi-gatt-server'
mainloop = None
class TxCharacteristic(Characteristic):
def __init__(self, bus, index, service):
Characteristic.__init__(self, bus, index, UART_TX_CHARACTERISTIC_UUID,
['notify'], service)
self.notifying = False
GLib.io_add_watch(sys.stdin, GLib.IO_IN, self.on_console_input)
def on_console_input(self, fd, condition):
s = fd.readline()
if s.isspace():
pass
else:
self.send_tx(s)
return True
def send_tx(self, s):
if not self.notifying:
return
value = []
for c in s:
value.append(dbus.Byte(c.encode()))
self.PropertiesChanged(GATT_CHRC_IFACE, {'Value': value}, [])
def StartNotify(self):
if self.notifying:
return
self.notifying = True
def StopNotify(self):
if not self.notifying:
return
self.notifying = False
class RxCharacteristic(Characteristic):
def __init__(self, bus, index, service):
Characteristic.__init__(self, bus, index, UART_RX_CHARACTERISTIC_UUID,
['write'], service)
def WriteValue(self, value, options):
print('remote: {}'.format(bytearray(value).decode()))
class UartService(Service):
def __init__(self, bus, index):
Service.__init__(self, bus, index, UART_SERVICE_UUID, True)
self.add_characteristic(TxCharacteristic(bus, 0, self))
self.add_characteristic(RxCharacteristic(bus, 1, self))
class Application(dbus.service.Object):
def __init__(self, bus):
self.path = '/'
self.services = []
dbus.service.Object.__init__(self, bus, self.path)
def get_path(self):
return dbus.ObjectPath(self.path)
def add_service(self, service):
self.services.append(service)
@dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
def GetManagedObjects(self):
response = {}
for service in self.services:
response[service.get_path()] = service.get_properties()
chrcs = service.get_characteristics()
for chrc in chrcs:
response[chrc.get_path()] = chrc.get_properties()
return response
class UartApplication(Application):
def __init__(self, bus):
Application.__init__(self, bus)
self.add_service(UartService(bus, 0))
class UartAdvertisement(Advertisement):
def __init__(self, bus, index):
Advertisement.__init__(self, bus, index, 'peripheral')
self.add_service_uuid(UART_SERVICE_UUID)
self.add_local_name(LOCAL_NAME)
self.include_tx_power = True
def find_adapter(bus):
remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
DBUS_OM_IFACE)
objects = remote_om.GetManagedObjects()
for o, props in objects.items():
if LE_ADVERTISING_MANAGER_IFACE in props and GATT_MANAGER_IFACE in props:
return o
print('Skip adapter:', o)
return None
def main():
global mainloop
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
adapter = find_adapter(bus)
if not adapter:
print('BLE adapter not found')
return
service_manager = dbus.Interface(
bus.get_object(BLUEZ_SERVICE_NAME, adapter),
GATT_MANAGER_IFACE)
ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
LE_ADVERTISING_MANAGER_IFACE)
app = UartApplication(bus)
adv = UartAdvertisement(bus, 0)
mainloop = GLib.MainLoop()
service_manager.RegisterApplication(app.get_path(), {},
reply_handler=register_app_cb,
error_handler=register_app_error_cb)
ad_manager.RegisterAdvertisement(adv.get_path(), {},
reply_handler=register_ad_cb,
error_handler=register_ad_error_cb)
try:
mainloop.run()
except KeyboardInterrupt:
adv.Release()
if __name__ == '__main__':
main()
3. Test
3-1. Run the UART service on Raspberry Pi.
python uart_peripheral.py
If all goes well, the output should be like this. Now, the service is running on Raspberry Pi and it’s broadcasting BLE advertising message.
* See Troubleshoot when you get error.
$ python uart_peripheral.py
GATT application registered
GetAll
returning props
Advertisement registered
3-2. Lauch nRF Toolbox app on the smartphone and tap on “UART”.
3-3. Tap on “CONNECT” button. Then the app will start scanning for nearby BLE devices.
3-4. Select your Raspberry Pi from the detected device list. It triggers the connection between the Raspberry Pi and the app.
* In case of iPhone, Raspberry Pi’s host name may be displayed instead of LOCAL_NAME in the code.
3-5. Tap on “Show Log”, enter some strings and tap on “Send”.
3-7. Check the console on Raspberry Pi. You should be able to see what you sent from the app like below.
remote: hello from iphone!
3-8. Let’s send back something from Raspberry Pi by entering below on Raspberry Pi’s console.
hello from Raspberry Pi!
3-9. Check the app screen.
Troubleshoot
If you are getting “Failed to register advertisement” error below, it’s most likely because the advertisement wasn’t unregistered when the script exited previously.
Failed to register advertisement: org.bluez.Error.Failed: Failed to register advertisement
An easy way to recover is to restart bluetooth service.
sudo systemctl restart bluetooth.service
References
[1] UART/Serial Port Emulation over BLE – Nordic Semiconductor
[2] nRF Toolbox App – Nordic Semiconductor
[3] Turning a Raspberry Pi 3 into a Bluetooth Low Energy peripheral