Open robino16 opened 3 years ago
So I've found a solution. And it may not be an issue with bluepy. I had to run a custom bluez Python agent in a separate thread. Once my custom agent runs, I can issue pair() without any prompt showing up.
Hello @robino16 Could you explain how did you use the custom bluez Python agent. I am also facing the same issue. I need to connect to a sensor that requires a passkey to see its services. I am using bluepy
I'll try my best @abhishek-v-pandey .
I basically run my bluepy Peripheral in one thread and my custom agent in another thread. I based my agent on ukBaz's example agent as shown in this thread. Then I modified it to not request user authorization, and simply run it in it's own thread once my application start, like this:
from threading import Thread
# ...
agent = Agent(bus, AGENT_PATH)
manager = dbus.Interface(bus.get_object(BUS_NAME, AGNT_MNGR_PATH), ANGT_MNGR_IFACE)
manager.RegisterAgent(AGENT_PATH, CAPABILITY)
manager.RequestDefaultAgent(AGENT_PATH)
adapter = Adapter()
main_loop = GLib.MainLoop()
# start the thread
def run_gbus():
main_loop.run()
t1 = Thread(run_gbus)
t1.start()
# after this, I connect and pair with my Peripheral
peripheral = Peripheral('aa:bb:cc:dd:ee:ff', 'random')
peripheral.connect()
# ...
peripheral.pair()
I hope this answers your question. I'm not sure how this works with Passkey authentication.
Edit: The run_gbus() function should not catch the KeyboardInterrupt event. It's better to stop the main_loop using main_loop.stop() before exiting the program.
Thanks @robino16 for the code. I tried it . But no progess, I still could not connect to my device. It is a BLE sensor which requires a passkey to autenticate.
Below is my code.
from threading import Thread
import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib
import sys
import time
from bluepy import btle
BUS_NAME = 'org.bluez'
ADAPTER_IFACE = 'org.bluez.Adapter1'
ADAPTER_ROOT = '/org/bluez/hci'
AGENT_IFACE = 'org.bluez.Agent1'
AGNT_MNGR_IFACE = 'org.bluez.AgentManager1'
AGENT_PATH = '/my/app/agent'
AGNT_MNGR_PATH = '/org/bluez'
CAPABILITY = 'KeyboardDisplay'
DEVICE_IFACE = 'org.bluez.Device1'
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
def set_trusted(path):
props = dbus.Interface(bus.get_object(BUS_NAME, path), dbus.PROPERTIES_IFACE)
props.Set(DEVICE_IFACE, "Trusted", True)
class Agent(dbus.service.Object):
@dbus.service.method(AGENT_IFACE,
in_signature="", out_signature="")
def Release(self):
print("Release")
@dbus.service.method(AGENT_IFACE,
in_signature='o', out_signature='s')
def RequestPinCode(self, device):
print(f'RequestPinCode {device}')
return '0000'
@dbus.service.method(AGENT_IFACE,
in_signature="ou", out_signature="")
def RequestConfirmation(self, device, passkey):
print("RequestConfirmation (%s, %06d)" % (device, passkey))
set_trusted(device)
return
@dbus.service.method(AGENT_IFACE,
in_signature="o", out_signature="")
def RequestAuthorization(self, device):
print("RequestAuthorization (%s)" % (device))
auth = input("Authorize? (yes/no): ")
if (auth == "yes"):
return
raise Rejected("Pairing rejected")
@dbus.service.method(AGENT_IFACE,
in_signature="o", out_signature="u")
def RequestPasskey(self, device):
print("RequestPasskey (%s)" % (device))
set_trusted(device)
passkey = input("Enter passkey: ")
return dbus.UInt32(passkey)
@dbus.service.method(AGENT_IFACE,
in_signature="ouq", out_signature="")
def DisplayPasskey(self, device, passkey, entered):
print("DisplayPasskey (%s, %06u entered %u)" %
(device, passkey, entered))
@dbus.service.method(AGENT_IFACE,
in_signature="os", out_signature="")
def DisplayPinCode(self, device, pincode):
print("DisplayPinCode (%s, %s)" % (device, pincode))
class Adapter:
def __init__(self, idx=0):
bus = dbus.SystemBus()
self.path = f'{ADAPTER_ROOT}{idx}'
self.adapter_object = bus.get_object(BUS_NAME, self.path)
self.adapter_props = dbus.Interface(self.adapter_object,
dbus.PROPERTIES_IFACE)
self.adapter_props.Set(ADAPTER_IFACE,
'DiscoverableTimeout', dbus.UInt32(0))
self.adapter_props.Set(ADAPTER_IFACE,
'Discoverable', True)
self.adapter_props.Set(ADAPTER_IFACE,
'PairableTimeout', dbus.UInt32(0))
self.adapter_props.Set(ADAPTER_IFACE,
'Pairable', True)
agent = Agent(bus, AGENT_PATH)
manager = dbus.Interface(bus.get_object(BUS_NAME, AGNT_MNGR_PATH), AGNT_MNGR_IFACE)
manager.RegisterAgent(AGENT_PATH, CAPABILITY)
manager.RequestDefaultAgent(AGENT_PATH)
adapter = Adapter()
def run_gbus():
main_loop = GLib.MainLoop()
try:
main_loop.run()
except KeyboardInterrupt:
manager.UnregisterAgent(AGENT_PATH)
main_loop.quit()
t1 = Thread(target = run_gbus)
t1.start()
# after this, I connect and pair with my Peripheral
peripheral = btle.Peripheral('00:1e:ae:3d:d8:1c')
peripheral.connect()
peripheral_A = peripheral.getServiceByUUID("65c4f2e0-b19e-11e2-9e96-0800200c9a66")
print(peripheral_A)
Below is the error: bluepy.btle.BTLEDisconnectError: Failed to connect to peripheral 00:1e:ae:3d:d8:1c, addr type: public
Please help and tell where I am wrong and what changes should i make.
Thanks
Hi, @abhishek-v-pandey.
I'm sorry, but I have no idea what the problem is. I don't think it's the agents fault. It seems you fail to establish the connection with the device. In my app i use addr type random. I have read other places that people first use pair(), and then connect(). But I have far too little knowledge about bluepy to guess why you get the BTLEDisconnectError. I usually get this error when my peripheral device is not advertising/switched off.
My device does not require a PassKey to pair. I pair using JustWorks with no security. After this, the peripheral whitelists the central device, so that no other device may connect. I have not had any issues with connecting to my device.
Edit: JustWorks is a pairing mechanism.
Hi @robino16 , I am using Raspberry Pi 4 to connect with the sensor. With nRF connect app I am able to connect to the sensor, it ask for the passkey but with the Pi its shows the above error.
Do you think this makes a difference by the way it connects.? How it connection works with you using bluepy.? Any special code we should write for working with security like JustWorks?
Thanks
Hi, @abhishek-v-pandey.
I wrote a bit wrong. JustWorks is a pairing mechanism. It simply means that no PassKey or user input should be required during the pairing process. My Issue was that I always was asked for user authorization when I tried to pair. I believe the peripheral.connect()
does not relate to JustWorks. But it still seems like your peripheral refuses to connect. Perhaps you need to make modifications to the firmware.
When I use peripheral.connect()
, I do not get a BTLEDisconnectError. My only annoyance was that a prompt showed up every time I called peripheral.pair()
.
Hi @robino16 ,
I studied about pairing mechanism. The sensor i have is not designed by me. It uses passkey to authorize it. Firmware change is not possible. I need to make the R.Pi to connect with it.
Today I tried to pair it with using blueman and hcitool command. I can trust my device using blueman. When I enter the command sudo hcitool lecc "mac_address" . i get a prompt to enter the passkey and then afterwards its get bonded. I can then see it trusted and bonded in the blueman but when i run the python scripts it throws an error. I have tried to disable blueman and try as well.
I am using Bluez v5.50. do you think this is a version problem or some issues related to the bluez stack that the sensor is refusing the connection. With android device , it connects and android does not use the bluez.
Any other method you could advise that I can try to connect to my sensor. I can see the sensor advertisement packet while scanning using bluepy?
Thanks for the help !
When pairing, a dialogue box always appears, asking the end user to accept. If using bluetoothctl from a terminal with a NoInputNoOutput agent, the prompt appear in the command line instead, where the user have to write "yes" in order to accept the pairing.
I am using a custom made BLE device which uses Just Works pairing.
Python code:
If I enable Simple Secure Pairing on the Raspberry Pi using:
the peripheral.pair() method raises an exception with "Authentication Failed" (error code5). If using bluetoothctl in command line (or using subprocess in Python) like:
the same prompt instead occur inside the terminal, once the code is executed. The user has to type "yes" in the terminal in order to approve it.