Bluetoothctl or gatttool for BLE notifications from multiple devices - bluetooth

I have read many questions on the topic but found no information on how to best (or if it is even possible) to receive notifications from more than 1 device at a time using any library or API (preferably command line or Python library).
After connecting to different devices e.g. Heart Rate monitor and Phone, or two HR monitors, is there a way to receive data from 1 service from each device at the same time?
EDIT:
I have managed to connect different devices of the same characteristics and been able to get notifications from them using Bluetoothctl (part of Bluez) so long as I don't request the same service, which answers my question partially; still, does anyone know of a better way to do this?

1) First of all there is a github python project that does just that in Linux on Raspberry Pi: https://github.com/IanHarvey/bluepy
2) Second this snipet from Anthony Chiu uses that API to communicate with multiple devices using notifications:
from bluepy.btle import Scanner, DefaultDelegate, Peripheral
import threading
class NotificationDelegate(DefaultDelegate):
def __init__(self, number):
DefaultDelegate.__init__(self)
self.number = number
def handleNotification(self, cHandle, data):
print 'Notification:\nConnection:'+str(self.number)+ \nHandler:'+str(cHandle)+'\nMsg:'+data
bt_addrs = ['00:15:83:00:45:98', '00:15:83:00:86:72']
connections = []
connection_threads = []
scanner = Scanner(0)
class ConnectionHandlerThread (threading.Thread):
def __init__(self, connection_index):
threading.Thread.__init__(self)
self.connection_index = connection_index
def run(self):
connection = connections[self.connection_index]
connection.setDelegate(NotificationDelegate(self.connection_index))
while True:
if connection.waitForNotifications(1):
connection.writeCharacteristic(37, 'Thank you for the notification!')
while True:
print'Connected: '+str(len(connection_threads))
print 'Scanning...'
devices = scanner.scan(2)
for d in devices:
print(d.addr)
if d.addr in bt_addrs:
p = Peripheral(d)
connections.append(p)
t = ConnectionHandlerThread(len(connections)-1)
t.start()
connection_threads.append(t)
3) I will just write the manual connection option with bluetoothctl that you probably tried. Since it wasn't listed here, I will add that too:
**Manual connection with bluetoothctl: ** To get the list of characteristics you can use the “list-attributes” command after establing connection and entering Generic Attribute Submenu through menu gatt in bluetoothctl, which should print the same list as above:
list-attributes 00:61:61:15:8D:60
To read an attribute you first select it, with the -you guessed it- “select-attribute” command:
select-attribute /org/bluez/hci0/dev_00_61_61_15_8D_60/service000a/char000b
After that you can issue the “read” command, without any parameters.
To read a characteristic continuously (if characteristic supports it) , use the “notify” command:
notify on
PS: This is my first answer on stackoverflow :)
I am also new to BLE, so bear with me. Am interested in your project, any links / contact are appreciated :)
You can find me on alexandrudancu.com

Related

Read and notification issues with gattlib BLE?

I am writing a Linux application using the gattlib library in python3 to send and receive user inputted data between a BlueSnap DB9 BLE adapter and my Linux device. I have been able to successfully send a String of data to the adapter from my device and seen the output on the adapter's terminal, but I am having issues receiving data from the adapter.
I am following this example for reading and writing data using the gattlib library. I can write data using the write_cmd and write_by_handle functions but I am unable to read data or enable notifications with gattlib using any of the read functions mentioned there. Notifications don't appear to be enabled when using gattlib because the on_notification function I overwrote doesn't print out the print statement I added there.
I have determined that the handles for writing and reading data are 0x0043 and 0x0046, respectively. Here are the UUIDs for writing and reading that serialio provided to me: UUIDs.
When using bluetoothctl, after selecting the characteristic, I am able to write data to the adapter. Only after enabling notifications on bluetoothctl, only then am I able to read data as well. Once I disable notifications, attempting to read manually prints out all 0s instead of the data I want to read. What is the proper way to select a characteristic and enable notifications using gattlib in python3?
UPDATE: I was able to get notifications enabled. I ran hcidump on both bluetoothctl and my python code and determined the handle I had used for enabling notifications was incorrect. The correct handle for enabling notifications is 0x0047. Once I realized this mistake, I ran enable_notifications using the correct handle and set both parameters to True and was able to enable notifications and see incoming data on my device's terminal as I typed it on my adapter's terminal.
Not using gattlib but here is an example of using the Python3 D-Bus bindings and the GLib event loop to read, write and get notifications from GATT characteristics.
from time import sleep
import pydbus
from gi.repository import GLib
# Setup of device specific values
dev_id = 'DE:82:35:E7:43:BE'
adapter_path = '/org/bluez/hci0'
device_path = f"{adapter_path}/dev_{dev_id.replace(':', '_')}"
temp_reading_uuid = 'e95d9250-251d-470a-a062-fa1922dfa9a8'
temp_period_uuid = 'e95d1b25-251d-470a-a062-fa1922dfa9a8'
# Setup DBus informaton for adapter and remote device
bus = pydbus.SystemBus()
mngr = bus.get('org.bluez', '/')
adapter = bus.get('org.bluez', adapter_path)
device = bus.get('org.bluez', device_path)
# Connect to device (needs to have already been paired via bluetoothctl)
device.Connect()
# wait for GATT services to be discovered
while not device.ServicesResolved:
sleep(0.5)
# Some helper functions
def get_characteristic_path(device_path, uuid):
"""Find DBus path for UUID on a device"""
mng_objs = mngr.GetManagedObjects()
for path in mng_objs:
chr_uuid = mng_objs[path].get('org.bluez.GattCharacteristic1', {}).get('UUID')
if path.startswith(device_path) and chr_uuid == uuid:
return path
def as_int(value):
"""Create integer from bytes"""
return int.from_bytes(value, byteorder='little')
# Get a couple of characteristics on the device we are connected to
temp_reading_path = get_characteristic_path(device._path, temp_reading_uuid)
temp_period_path = get_characteristic_path(device._path, temp_period_uuid)
temp = bus.get('org.bluez', temp_reading_path)
period = bus.get('org.bluez', temp_period_path)
# Read value of characteristics
print(temp.ReadValue({}))
# [0]
print(period.ReadValue({}))
# [232, 3]
print(as_int(period.ReadValue({})))
# 1000
# Write a new value to one of the characteristics
new_value = int(1500).to_bytes(2, byteorder='little')
period.WriteValue(new_value, {})
# 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()

python 3 - jack-client - using write_midi_event - jack keeps sending the midi event forever

I try to use python jack-client module to send a program change midi when I click on a button
here is a simplified version of the code :
def process_callback(frames: int):
global midiUi
if(midiUi is not None):
midiUi.process_callback(frames)
class MidiUi:
def __init__(self):
self.client = jack.Client('MidiUi')
self.client.set_process_callback(process_callback)
self.client.activate()
def sendProgramChange(self):
self.midiQueue.append([0xC0,0])
def process_callback(self,frames: int):
while(len(self.midiQueue)>0):
data = self.midiQueue.pop()
self.outMidiPort.clear_buffer()
buffer = self.outMidiPort.reserve_midi_event(0,len(data))
buffer[:] = bytearray(data)
self.outMidiPort.write_midi_event(0,buffer) # this only happens once yet midi input receives tons of program changes events
#raise jack.CallbackExit
midiUi = MidiUi()
while True:
....
#some button calls midiUi.sendProgramChange()
write_midi_event is called only once when pressing the button,
but apparently the destination midi port receives a continuous flow of midi C0 program changes (unless I call jack.CallbackExit, but then the call back never triggers again)
(I monitor my python script output using jack_midi_dump and midisnoop)
anyone know how to solve this ?
thanks for your help
I now user python-rtmidi for this matter
midiout = rtmidi.MidiOut(rtapi=rtmidi.API_UNIX_JACK)
rtMidiOutputPorts=midiout.get_ports()
then write data to the port
This post may be kind of old and it seems like you've got it figured out, but I did find a solution:
The midi client is sending whatever is in the buffer, which means that things like write_midi_event need to be cleared to stop sending it. Therefore, what helped me was near the beginning of my process, I had;
outport.clear_buffer()
Hope that helps ;)

Micropython and Bluetooth on ESP32

I know the support for bluetooth is still under development but it seems to cover everything I need at this point so I decided to give it a try.
I just want to simulate reading from a source of data (a EKG machine) so I came up with this code:
from ubluetooth import BLE
from ubluetooth import FLAG_READ, FLAG_NOTIFY, FLAG_WRITE
import time
ekg_data = [-305,-431,-131,440 ,1158,1424,1445,1623,1500,1018,142 ,-384,-324,-414,-77 ,334 ,-372,-154,366 ,7613,1461,1403,6133,-179,-381,-224,-135,-168,-208,-187,-181,-180,-160,-160,-151,-150,-151,-138,-141,-128,-118,-106,-798,-677,-430,-253,-122,98 ,133 ,281 ,354 ,390 ,519 ,475 ,558 ,565 ,533 ,593 ,458 ,377 ,107 ,-335,-719,-116,-129,-132,-131,-119,-122,-111,-106,-105,-935,-971,-877,-841,-841,-725,-757,-660,-641,-660,-554,-592,-496,-473,-486,-387,-431,-350,-364,-347,-208,-365,-362]
bt = BLE()
bt.active(True)
print('----')
print(bt.config('mac'))
print(bt.config('gap_name'))
HR_UUID = bluetooth.UUID(0x180D)
HR_CHAR = (bluetooth.UUID(0x2A37), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
HR_SERVICE = (HR_UUID, (HR_CHAR,),)
SERVICES = (HR_SERVICE,)
((ekg,),) = bt.gatts_register_services(SERVICES)
# bt.gap_advertise(100, 'MicroPython EKG')
count = 0
while True:
if count >= len(ekg_data):
count = 0
bt.gatts_write(ekg, ekg_data[count].to_bytes(2, 'big'))
print(ekg_data[count])
time.sleep_ms(1000)
count += 1
Now the code compiles and runs (I can see the output on the console) but I cannot find the device in my bluetooth app (I am using the nordic app)
Does anyone with more knowledge on that area can tell me if I am overlooking something? I tried to take the advertising off and on because I thought I might be overriding something with it but that didn't help too...
I think your code is missing multiple things.
First, you are not setting (irq) which is (Event Handling) for Micropython(As you can see from the docs or in their Github codes.
Also, I can't see you setting the buffer or any stuff like that, please revise the examples for what you asking here. Good job btw.

Check if audio playing with Python on Windows 10

I'm working with Python 3.7 on Windows 10.
I would like to detect if there is any audio playing on my computer or not.
I was looking into win32api.GetVolumeinformation but I'm unable to get what I want.
When you control your audio you can see if there is a program playing and I want to achieve that.
Try this api using winrt:
The enum options are listed here, but you can use mediaIs("PAUSED"), mediaIs("PLAYING") ect...
import asyncio, winrt.windows.media.control as wmc
async def getMediaSession():
sessions = await wmc.GlobalSystemMediaTransportControlsSessionManager.request_async()
session = sessions.get_current_session()
return session
def mediaIs(state):
session = asyncio.run(getMediaSession())
if session == None:
return False
return int(wmc.GlobalSystemMediaTransportControlsSessionPlaybackStatus[state]) == session.get_playback_info().playback_status #get media state enum and compare to current main media session state
There are heaps more useful winrt APIs to control media on windows too here.

Getting Data from PolarH10 via BLE

I have been trying to get data from my PolarH10 with my raspberry-pi. I have been successfully getting data through the commandline with bluez, but have been unable to reproduce that in python. I am using pygatt(gatttool bindings) and python3.
I have been closely following the examples provided on bitbucket and was able to detect my device and filter out it's MAC address by filtering it by name. I however was unable to get either of the "reading data asyncronously" examples to work.
#This doesnt work...
req = gattlib.GATTRequester(mymac)
response = gattlib.GATTResponse()
req.read_by_handle_async(0x15, response) # what does the 0x15 mean?
while not response.received():
time.sleep(0.1)
steps = response.received()[0]
...
#This doesn't work either
class NotifyYourName(gattlib.GATTResponse):
def on_response(self, data):
print("your data is: {}".format(data))
response = NotifyYourName()
req = gattlib.GATTRequester(mymac)
req.read_by_handle_async(0x15, response)
while True:
# here, do other interesting things
time.sleep(1)
I don't know and cannot extract from the "documentation(s)" how to subscribe to/read notifications from a characteristic(heart rate) of my sensor(PolarH10). The error I am getting is when calling GATTRequester.connect(True) is
RuntimeError: Channel or attrib not ready.
Please tell me how correctly connect to a BLE device via Python on Debian and how to programatically identify offered services and their characteristics and how to get their notifications asyncronously in python using gattlib(pygatt) or any other library. Thanks!
The answer is: Just use bleak.
I have a device that presents the same behavior. In my case, the problem was that it does not have a channel of type public, I should use random instead (like in gatttool -b BE:BA:CA:FE:BA:BE -I -t random).
Just calling the connect() method with the parameter channel_type to random could fix it:
requester.connect(True, channel_type="random")
PD: Sorry for the late response (maybe it will be helpful to others).

Resources