1

I had a successful attempts with BlueZ-5.50 > test > example-gatt-client.py with ESP32 set as HeartRate Sensor Server.I am able to read from ESP32 which i modified to suit the BlueZ example.

However i have a doubt. What if i need to send some byte or string to ESP32 from the existing example.I wrote in such manner (a simple function to see whether it throws an error):

def write_val():

value = 0x50

#Write to a UUID
cg_ctrl_pt_chrc.WriteValue(value,reply_handler=body_sensor_val_cb,
                                error_handler=generic_error_cb,
                                dbus_interface=GATT_CHRC_IFACE)    

I am still new & learning Dbus & Python.

OS - Raspbian Stretch

1
0

The examples in the BlueZ test directory use the dbus-python library which is not the best of python libraries according to https://wiki.python.org/moin/DbusExamples.

A library from that page is pydbus https://pypi.org/project/pydbus/ which seems to work well with the BlueZ DBus API for a GATT client.

I don't have an ESP32 or Heart Rate Sensor, so I will do an example with a BBC micro:bit and hopefully it will be easy to modify to your needs.

The BlueZ DBus API documentation is available at:

https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/adapter-api.txt

https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/device-api.txt

https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/gatt-api.txt

Some useful things to know to get you started:

  1. The Dbus service for bluez is called org.bluez
  2. The Bluetooth adapter on a Raspberry Pi normally has the DBus object path of /org/bluez/hci0
  3. The DBus Object path to a device is the adapter path plus the mac address prepended by 'dev_' and the semi-colons replaced with underscores. i.e. 'DE:82:35:E7:43:BE' would be found at /org/bluez/hci0/dev_DE_82_35_E7_43_BE

This script assumes that the device has already been paired with Raspberry Pi. As the pairing is a one-off provisioning step, it makes things simplier to do it manually. I usually use bluetoothctl on the command line.

import pydbus
from gi.repository import GLib

# Setup of device specific values
dev_id = 'DE:82:35:E7:43:BE'
btn_a_uuid = 'e95dda90-251d-470a-a062-fa1922dfa9a8'
temp_reading_uuid = 'e95d9250-251d-470a-a062-fa1922dfa9a8'
temp_period_uuid = 'e95d1b25-251d-470a-a062-fa1922dfa9a8'

# DBus object paths
bluez_service = 'org.bluez'
adapter_path = '/org/bluez/hci0'
device_path = f"{adapter_path}/dev_{dev_id.replace(':', '_')}"

bus = pydbus.SystemBus()
adapter = bus.get(bluez_service, adapter_path) 
device = bus.get(bluez_service, device_path)

# Assume device has been paired already so can use connect
device.Connect()
# Get commands and properties available
print(dir(adapter))
print(dir(device))

To read and write you need to find the path to the characteristic which is a little more work. Typically you know the UUID of the characteristic you are interested in so to get the value from button A on a BBC micro:bit it would be:

mngr = bus.get(bluez_service, '/')

def get_characteristic_path(dev_path, uuid):
    mng_objs = mngr.GetManagedObjects()
    for path in mng_objs:
        chr_uuid = mng_objs[path].get('org.bluez.GattCharacteristic1', {}).get('UUID')
        if path.startswith(dev_path) and chr_uuid == uuid:
           return path

char_path = get_characteristic_path(device._path, btn_a_uuid)
btn = bus.get(bluez_service, char_path)
print(btn.ReadValue({}))
# [0]

Writing to a characteristic is similar. Here is an example of reading and writing to the Temperature Period on a BBC micro:bit

tmp_period_path = get_characteristic_path(device._path, temp_period_uuid)
tmp_period = bus.get(bluez_service, tmp_period_path)
print(tmp_period.ReadValue({}))
# Result is:
# [232, 3]
# To get it as an integer:
print(int.from_bytes(tmp_period.ReadValue({}), byteorder='little'))
# 1000

# To write a new value of 1500
new_value = int(1500).to_bytes(2, byteorder='little')
tmp_period.WriteValue(new_value, {})
print(tmp_period.ReadValue({}))
# [220, 5]
device.Disconnect()

If you want to run this in an eventloop with notifications from the remote device then remove the disconnect above and add the following code:

temp_reading_path = get_characteristic_path(device._path, temp_reading_uuid)
temp = bus.get(bluez_service, temp_reading_path)

# Enable eventloop for notifications
def temp_handler(iface, prop_changed, prop_removed):
    """Notify event handler for temperature"""
    if 'Value' in prop_changed:
        print(f"Temp value: {as_int(prop_changed['Value'])} \u00B0C")

mainloop = GLib.MainLoop()
temp.onPropertiesChanged = temp_handler
temp.StartNotify()
try:
    mainloop.run()
except KeyboardInterrupt:
    mainloop.quit()
    temp.StopNotify()
    device.Disconnect()

Hope that is helpful.

16
  • Thank you ukBaz :) . I saw your example and gave a try. Correct me if i am wrong, this code will run only once right.So it does not do '.mainloop()' or is it part of 'connect();' Secondly, let say if i need to do 'Notify' or 'Indicate' , where and how to refer? I need to get sensor data from BLE Server which will change maybe every minutes. – R.M.S.Sundram Jul 10 '20 at 3:35
  • Then my next action will be moving the data to AWS server (which is not relevant here) but letting you know. – R.M.S.Sundram Jul 10 '20 at 3:41
  • Yes the original example just ran through once without a mainloop. Depending how frequently you wanted to read the sensor, you could put the original script in a crontab. I have extended the example to include a mainloop and notifications. With the notify example, you could change the notify handler to send the data to AWS server rather than printing as in my example does. – ukBaz Jul 10 '20 at 5:09
  • Thanks again for the help ukBaz.I was from embedded systems background (mostly C/C++) so working alongside with Python is a bit..well..feel unconnected. And dbus was whole new topic for me. Sure. I will give a try and approach you back if need any help. Thank You! – R.M.S.Sundram Jul 10 '20 at 7:46
  • Oh forgot : I tried the given example and sometimes it throws 'Software Reset Occured'. I this case how to check whether bluetooth has connected or not? Just want to have a quick reference with you. Sorry ukBaz – R.M.S.Sundram Jul 10 '20 at 8:33
0

I am posting another answer to see if this fits better given that you have posted the link which has the code you are using at the other end of the link:

https://platformio.org/lib/show/1841/ESP32%20BLE%20Arduino

Looking at the BLE_write.ino, my understanding is that if you write text to "beb5483e-36e1-4688-b7f5-ea07361b26a8" the ESP32 will display the text on its console.

import pydbus
from time import sleep

# Setup of device specific values
dev_id = 'DE:82:35:E7:43:BE'
# BBC micro:bit text display characteristic
# txt_uuid = 'E95D93EE-251D-470A-A062-FA1922DFA9A8'
# ESP32 BLE_write.ino text characteristic 
txt_uuid = 'beb5483e-36e1-4688-b7f5-ea07361b26a8'

txt_to_send = 'This is a test'

# DBus object paths
bluez_service = 'org.bluez'
adapter_path = '/org/bluez/hci0'
device_path = f"{adapter_path}/dev_{dev_id.replace(':', '_')}"
# Get adapter and device objects
bus = pydbus.SystemBus()
adapter = bus.get(bluez_service, adapter_path) 
device = bus.get(bluez_service, device_path)

# Assume device has been paired already so can use connect
device.Connect()

# Wait for the remote device to resolve its services
while not device.ServicesResolved:
    sleep(0.5)

mngr = bus.get(bluez_service, '/')

def get_characteristic_path(dev_path, uuid):
    """Get DBus object path for Characteristic UUID"""
    mng_objs = mngr.GetManagedObjects()
    for path in mng_objs:
        chr_uuid = mng_objs[path].get('org.bluez.GattCharacteristic1', {}).get('UUID')
        if path.startswith(dev_path) and chr_uuid == uuid.casefold():
           return path

txt_path = get_characteristic_path(device._path, txt_uuid)
txt_obj = bus.get(bluez_service, txt_path)
# Turn text into 
new_value = [ord(c) for c in txt_to_send]
# Write a new value ensure it is not too long for Bluetooth 
txt_obj.WriteValue(new_value[:19], {})

# Disconnect
device.Disconnect()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.