A little bit of background, I want to use my Pi to emulate a bluetooth keyboard for my phone.
I have actually managed to get it to work with my phone but I'm having a hard time with automatic connection.
I want the pi to scan nearby devices and initiate connection with already paired devices but for some reason it doesn't work.
If my pi is connected to a screen (my screen has a built in speaker) than for some reason automatic connection only connects to audio profiles and I have to manually connect to the HID profile, if my pi is not connected to a screen it just can't connect at all and I'm getting
org.bluez.Error.Failed: Protocol not available
when I'm trying to view the bluetooth status using sudo systemctl status bluetooth I get a hint about what protocol is missing.. this is the output:
● bluetooth.service - Bluetooth service
Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2021-08-11 11:43:46 IDT; 52min ago
Docs: man:bluetoothd(8)
Main PID: 627 (bluetoothd)
Status: "Running"
Tasks: 1 (limit: 4915)
CGroup: /system.slice/bluetooth.service
└─627 /usr/lib/bluetooth/bluetoothd -P input
Aug 11 11:57:37 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
Aug 11 12:00:53 raspberrypi bluetoothd[627]: Endpoint registered: sender=:1.96 path=/MediaEndpoint/A2DPSource
Aug 11 12:00:53 raspberrypi bluetoothd[627]: Endpoint registered: sender=:1.96 path=/MediaEndpoint/A2DPSink
Aug 11 12:01:10 raspberrypi bluetoothd[627]: Endpoint unregistered: sender=:1.96 path=/MediaEndpoint/A2DPSource
Aug 11 12:01:10 raspberrypi bluetoothd[627]: Endpoint unregistered: sender=:1.96 path=/MediaEndpoint/A2DPSink
Aug 11 12:04:43 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
Aug 11 12:05:02 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
Aug 11 12:28:12 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
Aug 11 12:28:27 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
Aug 11 12:28:40 raspberrypi bluetoothd[627]: a2dp-source profile connect failed for {DEVICE MAC}: Protocol not available
I feel like I've tried everything at this point, the lack of online documentation/information is really frustrating.
I have even purged pulseaudio completely from my device in hopes that it would stop trying to connect to audio profiles but it didn't work.
Here is some of the code that I think is relevant:
The device class
class BTKbDevice:
"""This class is used to define the bluetooth controller properties and capabilities"""
def __init__(self):
# Set up device
system_helper.init_device()
# log periodical scan results
ScanLogHelper().run()
# Declare class fields
self.server_control_port = None
self.server_interrupt_port = None
self.client_control_port = None
self.client_interrupt_port = None
# define some constants
self.__CONTROL_PORT = 17 # Service port - must match port configured in SDP record
self.__INTERRUPTION_PORT = 19 # Interrupt port - must match port configured in SDP record
self.__CURRENTLY_CONNECTED_DEVICE = "" # Used for logging connection/disconnection events
print("device started")
# listen for incoming client connections
def listen(self):
# We are not using BluetoothSocket constructor to have access to setsockopt method later
# instead we use the native socket equivalent
self.server_control_port = socket.socket(
socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) # BluetoothSocket(L2CAP)
self.server_interrupt_port = socket.socket(
socket.AF_BLUETOOTH, socket.SOCK_SEQPACKET, socket.BTPROTO_L2CAP) # BluetoothSocket(L2CAP)
# This allows the system to reuse the same port for different connections
# this is useful for situations where for some reason the port wasn't closed properly
# i.e. crashes, keyboard interrupts etc.
self.server_control_port.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_interrupt_port.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# bind these sockets to a port
# use BDADDR_ANY because we are only really interested in defining a constant port
self.server_control_port.bind((socket.BDADDR_ANY, self.__CONTROL_PORT))
self.server_interrupt_port.bind((socket.BDADDR_ANY, self.__INTERRUPTION_PORT))
# Start listening on the server sockets
self.server_control_port.listen(1)
self.server_interrupt_port.listen(1)
# Wait for connections
# the accept() method will block code execution until a connection was established
self.client_control_port, client_information = self.server_control_port.accept()
self.client_interrupt_port, client_information = self.server_interrupt_port.accept()
# We need to remember the connected device for disconnection logging
# client_information[0] is device's mac address
self.__CURRENTLY_CONNECTED_DEVICE = client_information[0]
def device_disconnected(self):
self.__CURRENTLY_CONNECTED_DEVICE = ""
def is_currently_connected_exists(self):
return self.__CURRENTLY_CONNECTED_DEVICE != ""
def get_currently_connected_device(self):
return self.__CURRENTLY_CONNECTED_DEVICE
# Cleanup
def close_connections(self):
self.server_control_port.close()
self.server_interrupt_port.close()
self.client_control_port.close()
self.client_interrupt_port.close()
The service class
class BTKbService(dbus.service.Object):
def __init__(self):
# set up as a dbus service
bus_name = dbus.service.BusName(
"org.thanhle.btkbservice", bus=dbus.SystemBus())
dbus.service.Object.__init__(
self, bus_name, "/org/thanhle/btkbservice")
print("service started. starting device")
# create and setup our device
self.device = BTKbDevice()
# start listening for connections
self.device.listen()
system_helper.py
"""A utility for handling system related operations and events"""
UUID = "00001124-0000-1000-8000-00805f9b34fb"
# Get available bluetooth devices list from system
def get_controllers_info():
return subprocess.getoutput("hcitool dev")
# Check if our device is available
def is_controller_available():
device_data = get_controllers_info()
return const.MY_ADDRESS in device_data.split()
# Handle device initialization
def init_device():
__init_hardware()
__init_bluez_profile()
# Configure the bluetooth hardware device
def __init_hardware():
# Reset everything to make sure there are no problems
os.system("hciconfig hci0 down")
os.system("systemctl daemon-reload")
os.system("/etc/init.d/bluetooth start")
# Activate device and set device name
os.system("hciconfig hci0 up")
os.system("hciconfig hci0 name " + const.MY_DEV_NAME)
# make the device discoverable
os.system("hciconfig hci0 piscan")
# set up a bluez profile to advertise device capabilities from a loaded service record
def __init_bluez_profile():
# read and return an sdp record from a file
service_record = __read_sdp_service_record()
# setup profile options
opts = {
"AutoConnect": True,
"RequireAuthorization": False,
"ServiceRecord": service_record
}
# retrieve a proxy for the bluez profile interface
bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object(
"org.bluez", "/org/bluez"), "org.bluez.ProfileManager1")
manager.RegisterProfile("/org/bluez/hci0", UUID, opts)
# Set device class
os.system("hciconfig hci0 class 0x0025C0")
def __read_sdp_service_record():
try:
fh = open(const.SDP_RECORD_PATH, "r")
except OSError:
sys.exit("Could not open the sdp record. Exiting...")
return fh.read()
def get_connected_devices_data():
return subprocess.getoutput("hcitool con")
def get_paired_devices_data():
return subprocess.getoutput("bluetoothctl paired-devices")
connection_helper.py
The method initiate_connection_with_devices_in_range get's called every 10 seconds or so by another file that isn't relevant to this problem.
""" Responsible for finding paired devices in range and attempting connection with them """
def initiate_connection_with_devices_in_range(nearby_devices):
print("init connection started")
# Get paired devices from system
paired_devices = system_helper.get_paired_devices_data()
print("Paired device:\n" + paired_devices)
# Check nearby devices for a match
# no need to request data from bus if no paired device is available
for device in nearby_devices:
mac, name = device
print("checking for device " + name + " " + mac)
if mac in paired_devices.split():
print(name + " is paired, let's attempt connection")
# Paired device found, try to connect
__attempt_connection()
def __attempt_connection():
print("attempting connection")
# Get reference for the bus object, and for the objects it manages
bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object("org.bluez", "/"),
"org.freedesktop.DBus.ObjectManager")
objects = manager.GetManagedObjects()
# Extract device objects from bus
all_devices = (str(path) for path, interfaces in objects.items() if
"org.bluez.Device1" in interfaces.keys())
# Extract only devices managed by our adapter
device_list = None
for path, interfaces in objects.items():
if "org.bluez.Adapter1" not in interfaces.keys():
continue
device_list = [d for d in all_devices if d.startswith(path + "/")]
if device_list is not None:
print(device_list)
# Devices found, attempt connection
for dev_path in device_list:
print("trying to connect keyboard profile with " + dev_path)
dev_obj = bus.get_object('org.bluez', dev_path)
methods = dbus.Interface(dev_obj, 'org.bluez.Device1')
props = dbus.Interface(dev_obj, dbus.PROPERTIES_IFACE)
try:
methods.Connect()
except Exception as e:
print("Exception caught in connect method! {}".format(e))
# this actually print Exception caught in connect method! org.bluez.Error.Failed: Protocol not available
If I manually connect from my phone it works just fine, only automatic connection is problematic at the moment.
ANY help would be appreciated, most of what I did so far is the result of trial and error so it's possible that I made a mistake somewhere
I think that at the moment the pi "wants" to connect as an audio device but lacks ability to do so as it is not connected to any hardware that will allow it.. So somehow I need to make it "forget" about the audio profiles.
I would gladly provide more information if needed.
This does seem a little back to front as you have the peripheral trying to connect to the phone. Have you looked at using ConnectProfile rather than Connect?
If you want to have the HID device initiating connection (or reconnection) to HID host (RPi reconnects to your phone), then that is a change in the SDP record. More information at the following gist
Related
Summary
A python based BLE Central program, running on Raspberry PI 4, is unable to discover specific BLE peripherals. The same program when run on a Linux machine is able to discover and connect to the the specific BLE peripherals.
The Linux machine was the development platform for the above program and the target machine for this program is a Raspberry Pi 4 (8GB Ram)
What has been done so far.
In order to find answers I have done the following:
Purpose: to elimnate any issues with BLE Peripheral: The specific BLE peripherals mentioned above are a custom hardware based on Nordic's nRF52832 and has been tested independently using nRF Connect as well as another Android app for discovery, connection and data transfer
Purpose: to see if other bluetooth tools are able to discover the specific BLE peripherals: hcitool and btmgmt was used to discover the specific BLE devices and both successfully discovered the devices. This was done both on Linux as well as Raspberry PI and the results were same - both succeeded in discovering the devices.
Next bluetoothctl was used to scan but it failed to discover the BLE devices that were discovered by hcitool and btmgmt. The same was true on Linux as well as Raspberry pi
However my python program based on D-Bus module is able to discover these BLE devices on Linux but not on Raspberry pi.
Version Numbers
Linux
Kernel : 5.4.0-126-generic,
bluetoothctl : 5.53,
bluetoothd : 5.53,
hcitool : 5.53,
btmgmt : 5.53,
python : 3.9.5
Raspberry PI
Kernel : 5.15.61-v,
bluetoothctl : 5.65,
bluetoothd : 5.65,
hcitool : 5.55,
btmgmt : 5.55,
python : 3.9.2
Grateful for some way forward or cues to solve this.
UPDATE 3/Oct/2022:
I further checked the bluetooth service status using sudo service bluetooth status and found there was a difference between Linux and Raspberry status but am unclear as to what this means especially the one relating to last 6 lines of status relating to raspberry pi
On Linux
sudo service bluetooth status
bluetooth.service - Bluetooth service
Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2022-10-03 10:42:15 IST; 4h 10min ago
Docs: man:bluetoothd(8)
Main PID: 998 (bluetoothd)
Status: "Running"
Tasks: 1 (limit: 18985)
Memory: 4.2M
CGroup: /system.slice/bluetooth.service
└─998 /usr/lib/bluetooth/bluetoothd --experimental
Oct 03 10:42:14 udu-Inspiron-7559 systemd[1]: Starting Bluetooth service...
Oct 03 10:42:15 udu-Inspiron-7559 bluetoothd[998]: Bluetooth daemon 5.53
Oct 03 10:42:15 udu-Inspiron-7559 systemd[1]: Started Bluetooth service.
Oct 03 10:42:15 udu-Inspiron-7559 bluetoothd[998]: Starting SDP server
Oct 03 10:42:15 udu-Inspiron-7559 bluetoothd[998]: Bluetooth management interface 1.14 initialized
Oct 03 10:42:36 udu-Inspiron-7559 bluetoothd[998]: Endpoint registered: sender=:1.75 path=/MediaEndpoint/A2DPSink/sbc
Oct 03 10:42:36 udu-Inspiron-7559 bluetoothd[998]: Endpoint registered: sender=:1.75 path=/MediaEndpoint/A2DPSource/sbc
On Raspberry Pi
sudo service bluetooth status
Loaded: loaded (/lib/systemd/system/bluetooth.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2022-10-02 19:22:56 IST; 19h ago
Docs: man:bluetoothd(8)
Main PID: 884 (bluetoothd)
Status: "Running"
Tasks: 1 (limit: 8986)
CPU: 446ms
CGroup: /system.slice/bluetooth.service
└─884 /usr/libexec/bluetooth/bluetoothd --experimental
Oct 02 19:22:56 kosha bluetoothd[884]: Battery Provider Manager created
Oct 02 19:22:56 kosha bluetoothd[884]: Adv Monitor Manager created with supported features:0x00000000, enabled features:0x00000000, max number of supported monitors:32, max number of supported patterns:16
Oct 02 19:22:56 kosha bluetoothd[884]: Endpoint registered: sender=:1.27 path=/MediaEndpoint/A2DPSink/sbc
Oct 02 19:22:56 kosha bluetoothd[884]: Endpoint registered: sender=:1.27 path=/MediaEndpoint/A2DPSource/sbc
Oct 03 11:42:55 kosha bluetoothd[884]: Path / reserved for Adv Monitor app :1.51
Oct 03 11:45:14 kosha bluetoothd[884]: Adv Monitor app :1.51 disconnected from D-Bus
Oct 03 11:45:19 kosha bluetoothd[884]: Path / reserved for Adv Monitor app :1.52
Oct 03 13:58:28 kosha bluetoothd[884]: Adv Monitor app :1.52 disconnected from D-Bus
Oct 03 14:10:52 kosha bluetoothd[884]: Path / reserved for Adv Monitor app :1.72
Oct 03 14:26:38 kosha bluetoothd[884]: Adv Monitor app :1.72 disconnected from D-Bus
UPDATE 7/Oct/2022
A skeletal code replicating the behaviour of the python App below, however not having access to the BLE peripherals will prevent you from seeing the problem (i.e. Works fine on Linux 20.04 but not on Raspberry PI)
#!/usr/bin/python
# SPDX-License-Identifier: LGPL-2.1-or-later
import dbus
import dbus.mainloop.glib
from gi.repository import GLib
import sys, signal
BLUEZ_SERVICE_NAME = "org.bluez"
BLUEZ_NAMESPACE = "/org/bluez/"
DBUS_OM_PROPERTIES="org.freedesktop.DBus.Properties"
DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
ADAPTER_INTERFACE = BLUEZ_SERVICE_NAME + ".Adapter1"
DEVICE_INTERFACE = BLUEZ_SERVICE_NAME + ".Device1"
relevant_ifaces = ( ADAPTER_INTERFACE, DEVICE_INTERFACE )
# looking for device with these addresses
addrs = ["D0:5F:64:52:00:01","D0:5F:64:52:13:45"]
log_detail = 1
log_added = 1
log_removed = 1
not_of_interest = 1
addrs = [x.replace(":","_") for x in addrs ]
print(f"Devices with mac address {addrs} are of interest")
def property_changed(interface, changed, invalidated, path):
global devices, current_path
iface = interface[interface.rfind(".") + 1:]
#print(f"[{interface}]")
if "Device1" in interface:
# check if it is device of interest - get "_" separated mac address from path
_devaddr = path[path.rfind("/")+5:]
if _devaddr in addrs:
pkeyval(changed,f"CHG: {_devaddr} ")
else:
if not_of_interest:
print(f"CHG: Device Not of interest : {_devaddr}")
else:
if not_of_interest:
print(f"CHG: Interface not of interest: {iface}")
def interfaces_added(path, interfaces):
for iface, props in interfaces.items():
if not(iface in relevant_ifaces) or log_added == 0:
continue
if iface in ADAPTER_INTERFACE:
print(f"ADD: Adapter [{path}]")
else:
# its a device interface : get name and address from props and path respectively
_devaddr = path[path.rfind("/")+5:]
_name = "None"
if "Name" in props.keys():
_name = props['Name']
print(f"ADD: Device {_devaddr} Name: {_name}" )
def interfaces_removed(path, interfaces):
for iface in interfaces:
if not(iface in relevant_ifaces) or log_removed == 0:
continue
if iface in DEVICE_INTERFACE:
_devaddr = path[path.rfind("/")+5:]
print(f"DEL: Device {_devaddr}")
def pkeyval(d,title=None):
''' given d dbus dict print key val : Warning no checking for types'''
for k,v in d.items():
print(f"{title} {dbus_to_python(k)} = {dbus_to_python(v)}")
def find_adapter():
objects=get_managed_objects()
for o, props in objects.items():
if GATT_MANAGER_INTERFACE in props.keys():
return o
def signal_handler(sig, frame):
#global buff, fname
global devices
try:
mainloop.quit()
except Exception as e:
log_msg.error(e)
print("Exiting")
sys.exit(0)
def get_managed_objects():
bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, "/"),DBUS_OM_IFACE)
return manager.GetManagedObjects()
def find_adapter(pattern=None):
return find_adapter_in_objects(get_managed_objects(), pattern)
def find_adapter_in_objects(objects, pattern=None):
bus = dbus.SystemBus()
for path, ifaces in objects.items():
adapter = ifaces.get(ADAPTER_INTERFACE)
if adapter is None:
continue
if not pattern or pattern == adapter["Address"] or \
path.endswith(pattern):
obj = bus.get_object(BLUEZ_SERVICE_NAME, path)
return dbus.Interface(obj, ADAPTER_INTERFACE)
raise Exception("Bluetooth adapter not found")
def dbus_to_python(data):
if isinstance(data, dbus.String):
data = str(data)
if isinstance(data, dbus.ObjectPath):
data = str(data)
elif isinstance(data, dbus.Boolean):
data = bool(data)
elif isinstance(data, dbus.Int64):
data = int(data)
elif isinstance(data, dbus.Int32):
data = int(data)
elif isinstance(data, dbus.Int16):
data = int(data)
elif isinstance(data, dbus.UInt16):
data = int(data)
elif isinstance(data, dbus.Byte):
data = int(data)
elif isinstance(data, dbus.Double):
data = float(data)
elif isinstance(data, dbus.Array):
data = [dbus_to_python(value) for value in data]
elif isinstance(data, dbus.Dictionary):
new_data = dict()
new_key = ""
for key in data.keys():
new_key = dbus_to_python(key)
new_data[new_key] = dbus_to_python(data[key])
data = new_data
return data
if __name__ == '__main__':
global bus
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
bus.add_signal_receiver(property_changed, bus_name="org.bluez",
dbus_interface="org.freedesktop.DBus.Properties",
signal_name="PropertiesChanged",
path_keyword="path")
bus.add_signal_receiver(interfaces_added, bus_name="org.bluez",
dbus_interface="org.freedesktop.DBus.ObjectManager",
signal_name="InterfacesAdded")
bus.add_signal_receiver(interfaces_removed, bus_name="org.bluez",
dbus_interface="org.freedesktop.DBus.ObjectManager",
signal_name="InterfacesRemoved")
## set up control-c handler
signal.signal(signal.SIGINT, signal_handler)
mainloop = GLib.MainLoop()
#mainloop = GObject.MainLoop()
## get adapter
adapter = find_adapter()
try:
## start discovery
adapter.StartDiscovery()
except Exception as e:
log_msg.error(e)
sys.exit(1)
mainloop.run()
The problem was narrowed down to a wrong value of Advertising Flag in the BLE Peripheral firmware. Though the flag was initialised to the right value ( 6 meaning BR/DR Not supported, General Discoverable) , this flag was getting reset to 0 as result of a bug in the firmware. Every subsequent advertising update had a zero value Advertising Flag .
Once the firmware bug was fixed the Raspberry PI started to discover and add the BLE peripheral to org.bluez
How did you arrive at the fact that advertising flags could be a problem?
The observation that hctool and btmon were discovering the BLE device but bluetoothctl was unable to discover the device was puzzling. This led me to check bluetoothd debug messages from adapter.c and device.c.
The logs indicated that bluetoohd discovers the BLE device. Inspecting the code in device.c indicated that bluetoothd failed to add the device to org.blue tree as advertising flags value was 0. This drew our attention to Firmware and rectify the bug relating the Advertising Flag
How did Linux recognise and add the BLE Device if 0 value Advertising Flags were a problem?
I don’t have a good answer as I could not reproduce this situation.
Here is one possibility of how this could have happened.
When the python program on Linux started device discovery , the value of Advertising Flags must have been 6 (the correct value, the firmware did initialise the flag values correctly) and therefore got added as an object in the org.bluez tree.
I'm trying to learn to communicate with a Bluetooth device using Python but my first steps fail.
I can successfully connect to a given device using bluetoothctl:
[bluetooth]# connect F5:EE:1C:40:21:44
Attempting to connect to F5:EE:1C:40:21:44
[CHG] Device F5:EE:1C:40:21:44 Connected: yes
Connection successful
[NEW] Primary Service (Handle 0xa9bd)
/org/bluez/hci0/dev_F5_EE_1C_40_21_44/service000a
00001801-0000-1000-8000-00805f9b34fb
Generic Attribute Profile
[NEW] Primary Service (Handle 0xa9bd)
/org/bluez/hci0/dev_F5_EE_1C_40_21_44/service000b
53300001-0023-4bd4-bbd5-a6920e4c5653
Vendor specific
[NEW] Characteristic (Handle 0x6f54)
/org/bluez/hci0/dev_F5_EE_1C_40_21_44/service000b/char000c
53300002-0023-4bd4-bbd5-a6920e4c5653
Vendor specific
[NEW] Characteristic (Handle 0x6604)
/org/bluez/hci0/dev_F5_EE_1C_40_21_44/service000b/char000e
53300003-0023-4bd4-bbd5-a6920e4c5653
Vendor specific
[NEW] Descriptor (Handle 0x0164)
/org/bluez/hci0/dev_F5_EE_1C_40_21_44/service000b/char000e/desc0010
00002902-0000-1000-8000-00805f9b34fb
Client Characteristic Configuration
but trying to connect via Python3's pybluez module results in an exception being raised:
sock=bluetooth.BluetoothSocket(bluetooth.RFCOMM)
sock.connect(("F5:EE:1C:40:21:44", 1))
...
BluetoothError Traceback (most recent call last)
<ipython-input-17-2af54455681d> in <module>
----> 1 sock.connect(("F5:EE:1C:40:21:44", 1))
~/.local/lib/python3.9/site-packages/bluetooth/bluez.py in connect(self, *args, **kwargs)
BluetoothError: [Errno 112] Host is down
What am I doing wrong here? Very likely I'm just missing the very basics of Bluetooth development - maybe you can give me a direction..
Looking at the log information you shared from when you connected with bluetoothctl, it looks like you are connecting to a Bluetooth Low Energy (BLE) device.
The commands you are you issuing with PyBlueZ is to connect to a Bluetooth Classic (BR/EDR) device and I suspect that is why the device is not responding.
PyBlueZ uses a deprecated API for BlueZ. The currently supported APIs are documented at:
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc
It is the Device API that is used for connecting to a device. Below is an example of how to use it with the generic D-Bus library pydbus
from time import sleep
import pydbus
DEVICE_ADDR = 'F5:EE:1C:40:21:44' # device address
# DBus object paths
BLUEZ_SERVICE = 'org.bluez'
device_path = f"/org/bluez/hci0/dev_{DEVICE_ADDR.replace(':', '_')}"
# setup dbus
bus = pydbus.SystemBus()
device = bus.get(BLUEZ_SERVICE, device_path)
# Connect to device
device.Connect()
sleep(10)
device.Disconnect()
I am working on a project that connects an Android device with a Raspberry Pi. The RPi needs to be treated like a deployable device that the user never needs to touch. For this reason, I am trying to write a startup batch script on the RPi that will allow the user to pair their Android with the PI.
My idea is that when you startup, this script will run, the user on their phone will try and connect to the RPi, and the RPi will automatically accept this connection.
Here is what I have so far
#!/bin/bash
bluetoothctl -- discoverable on
bluetoothctl -- pairable on
bluetoothctl -- agent on
bluetoothctl -- default-agent
The issue is, when I do it this way I don't get into the [bluetoothctl] prompt that I need to communicate with the Android.
When I run these commands (Without batch script) and try and pair with my Android I get
Request confirmation
[agent] Confirm passkey 861797 (yes/no): yes
And from here I simply need to input yes to instantiate the connection. The issue I'm seeing is 1: I don't know how to stay in the [bluetoothctl] prompt within the command line to communicate with the device and 2: I don't know how to send "Yes" to the prompt.
Again, the important thing for me is that the user never needs to do anything more to the RPi than start it up for deployment purposes. Is there a fix for my problem or perhaps a better way to do it all together?
For those interested, the bluetooth startup connection is in place so that I can send network information to the RPi and it can automatically connect itself to the network so that the main application communication will take place that way.
Here is the desired result of the script which I was able to do manually.
Using bluetoothctl in that manner can be problematic as it is not designed to be interactive in that way. As you have put Python as one of the tags, the intended way of accessing this functionality from Python (and other languages) is through the D-Bus API.
These are documented at: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc
And there are examples at: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/test
The confirmation is the RequestConfirmation in the agent API. You can also set discoverable and pairable with the adapter API. Using the API will also allow you to stop discoverable from timing out.
Once the phone has connected, you typically want to mark it as trusted so that it doesn't need to pair again. This is done with the device API.
Below is an example of setting these properties on the adapter with Python. I have left all of the Agent functions in although it is only the RequestConfirmation that is used. I have set it to always agree to whatever code it is sent which is what you asked for in your question.
This example Python script would replace your batch script
import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib
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)
if __name__ == '__main__':
agent = Agent(bus, AGENT_PATH)
agnt_mngr = dbus.Interface(bus.get_object(BUS_NAME, AGNT_MNGR_PATH),
AGNT_MNGR_IFACE)
agnt_mngr.RegisterAgent(AGENT_PATH, CAPABILITY)
agnt_mngr.RequestDefaultAgent(AGENT_PATH)
adapter = Adapter()
mainloop = GLib.MainLoop()
try:
mainloop.run()
except KeyboardInterrupt:
agnt_mngr.UnregisterAgent(AGENT_PATH)
mainloop.quit()
On my raspberry pi 4 with bluez the following will accept my android phones pairing without the need to type anything on the raspberry.
sudo apt install bluez-tools
sudo bt-agent -c DisplayOnly -p ~/pins.txt &
pins.txt:
00:00:00:00:00:00 *
* *
Note that adding -d to bt-agent did not work for, no matter the -c arguments. Hence the ampersand at the end.
I am attempting to communicate with a BraggMETER Interrogator that supports SCPI.
OS: Windows 10
Connection hardware: j5create JUH470 USB 3.0 Multi-Adapter Gigabit Ethernet / 3-Port USB 3.0 HUB
Part of my confusion: Should I access as a USB device or as a TCPIP device?
When I connect via Telnet, all goes well. The IP Address and Port are 10.0.0.10 and 3500.
> telnet
> open 10.0.0.10 3500
:IDEN?
:ACK:HBM FiberSensing:FS22SI v3.0:08:046 840 200 898:20190116
:STAT?
:ACK:1
In Python, I am usig the pyvisa library.
import easy_scpi as scpi
import pyvisa
DeviceAddress = '10.0.0.10'
DevicePort = '3500'
VISADevice = f'TCPIP0::{DeviceAddress}::{DevicePort}::SOCKET'
# Doesn't work either --> VISADevice = 'ASRL10::INSTR'
rm = pyvisa.ResourceManager()
print(rm.list_resources())
inst = rm.open_resource(VISADevice)
print(inst.query("*IDEN?"))
inst.close()
The error is always on rm.open_resource. I have tried numerous connection strings. They give different errors. Here are three of them:
pyvisa.errors.VisaIOError: VI_ERROR_INTF_NUM_NCONFIG (-1073807195): The interface type is valid but the specified interface number is not configured.
pyvisa.errors.VisaIOError: VI_ERROR_TMO (-1073807339): Timeout expired before operation completed.
pyvisa.errors.VisaIOError: VI_ERROR_RSRC_NFOUND (-1073807343): Insufficient location information or the requested device or resource is not present in the system.
Update 1
I downloaded National Instruments NI-Max and used their NI I/O trace. This connection string "works":
TCPIP::10.0.0.10::3500::SOCKET
However, I still get the timeout error. Tried ensuring that the newline termination character is sent and upped the timeout to 5 seconds (which did take effect, as it delayed the logging of the timeout error). No dice. Still gives a timeout error.
Update 2
While not the same setup, someone else reports a problem who is using an NI GPIB-to-USB card (GPIB-USB-HS). The common thread is a USB adapter...
https://community.keysight.com/thread/37567
I'm not able to comment so I'm commenting here
Have you tried using a normal socket?
import socket
DeviceAddress = '10.0.0.10'
DevicePort = '3500'
BUFSIZ = 1024
ADDR = (DeviceAddress, DevicePort)
cmd = "IDN?" # or "*IDEN?" as you've put?
braggMeterSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
braggMeterSocket.connect(self.ADDR)
braggMeterSocket.send(cmd + "\n") #maybe with new line depending on what the device terminator is.
mesg = braggMeterSocket.recv(BUFSIZ)
mesg = mesg.strip() # Remove \n at end of mesg
print(mesg)
The problem was that the device exects CRLF (carriage return plus linefeed) as an SCPI command terminator. I was only sending one of those two characters, "\n".
Python I/O does not adapt to the OS like some languages I have used, which will interpret "\n" as "\r\n" in some situations.
Likewise, NI-Max only sent the "\n" and omitted the "\r".
First off if anybody knows of a good tutorial for coding bluetooth on my raspberry pi zero w with python to turn on discovery, listen for a pair request, connect and save the paired device, and more, that would be awesome. My code for testing bluetooth discovery is below.
import bluetooth
print("performing inquiry...")
nearby_devices = bluetooth.discover_devices(
duration=8, lookup_names=True, flush_cache=True)
print("found %d devices" % len(nearby_devices))
for addr, name in nearby_devices:
try:
print(" %s - %s" % (addr, name))
except UnicodeEncodeError:
print(" %s - %s" % (addr, name.encode('utf-8', 'replace')))
The TraceBack is below
Traceback (most recent call last):
File "bluetoothConnect.py", line 6, in <module>
duration=8, lookup_names=True, flush_cache=True)
File "/usr/lib/python2.7/dist-packages/bluetooth/bluez.py", line 17, in discover_devices
sock = _gethcisock ()
File "/usr/lib/python2.7/dist-packages/bluetooth/bluez.py", line 226, in _gethcisock
raise BluetoothError ("error accessing bluetooth device")
bluetooth.btcommon.BluetoothError: error accessing bluetooth device
("error accessing bluetooth device") is the clue.
Yes - as earlier mentioned you need elevated privileges.
Simply run the script with sudo...
eg - sudo python myscript.py
enter your password
It should now work..for testing purposes.
Though going forward I would create a privileged user and add that user to the root group with the setting /bin/false.
Then use that user to run all your scripts..
Had the same issue and then I wrote this simple script to wrap Bluetoothctl using python3. Tested on Raspberry pi 4.
import subprocess
import time
def scan(scan_timeout=20):
""" scan
Scan for devices
Parameters
----------
scan_timeout : int
Timeout to run the scan
Returns
----------
devices : dict
set of discovered devices as MAC:Name pairs
"""
p = subprocess.Popen(["bluetoothctl", "scan", "on"])
time.sleep(scan_timeout)
p.terminate()
return __devices()
def __devices():
""" devices
List discovered devices
Returns
----------
devices : dict
set of discovered devices as MAC:Name pairs
"""
devices_s = subprocess.check_output("bluetoothctl devices", shell=True).decode().split("\n")[:-1]
devices = {}
for each in devices_s:
devices[each[7:24]] = each[25:]
return devices
def info():
""" Info
Returns
----------
info : str
information about the device connected currently if any
"""
return subprocess.check_output("bluetoothctl info", shell=True).decode()
def pair(mac_address):
""" pair
Pair with a device
Parameters
----------
mac_address : str
mac address of the device tha you need to pair
"""
subprocess.check_output("bluetoothctl pair {}".format(mac_address), shell=True)
def remove(mac_address):
""" remove
Remove a connected(paired) device
Parameters
----------
mac_address : str
mac address of the device tha you need to remove
"""
subprocess.check_output("bluetoothctl remove {}".format(mac_address), shell=True)
def connect(mac_address):
""" connect
Connect to a device
Parameters
----------
mac_address : str
mac address of the device tha you need to connect
"""
subprocess.check_output("bluetoothctl connect {}".format(mac_address), shell=True)
def disconnect():
""" disconnect
Disconnects for currently connected device
"""
subprocess.check_output("bluetoothctl disconnect", shell=True)
def paired_devices():
""" paired_devices
Return a list of paired devices
"""
return subprocess.check_output("bluetoothctl paired-devices", shell=True).decode()