Instantly share code, notes, and snippets.

@pylover /a2dp.py
Last active Nov 20, 2018

Embed
What would you like to do?
Fixing bluetooth stereo headphone/headset problem in ubuntu 16.04, 16.10 and also debian jessie, with bluez5.
#! /usr/bin/env python3.5
"""
Fixing bluetooth stereo headphone/headset problem in ubuntu 16.04 and also debian jessie, with bluez5.
Workaround for bug: https://bugs.launchpad.net/ubuntu/+source/indicator-sound/+bug/1577197
Run it with python3.5 or higher after pairing/connecting the bluetooth stereo headphone.
This will be only fixes the bluez5 problem mentioned above .
Licence: Freeware
See ``python3.5 a2dp.py -h``.
Shorthands:
$ alias speakers="a2dp.py 10:08:C1:44:AE:BC"
$ alias headphones="a2dp.py 00:22:37:3D:DA:50"
$ alias headset="a2dp.py 00:22:37:F8:A0:77 -p hsp"
$ speakers
Check here for the latest updates: https://gist.github.com/pylover/d68be364adac5f946887b85e6ed6e7ae
Thanks to:
* https://github.com/DominicWatson, for adding the ``-p/--profile`` argument.
* https://github.com/IzzySoft, for mentioning wait before connecting again.
* https://github.com/AmploDev, for v0.4.0
* https://github.com/Mihara, for autodetect & autorun service
* https://github.com/dabrovnijk, for systemd service
Change Log
----------
- 0.5.2
* Increasing the number of tries to 15.
- 0.5.2
* Optimizing waits.
- 0.5.1
* Increasing WAIT_TIME and TRIES
- 0.5.0
* Autodetect & autorun service
- 0.4.1
* Sorting device list
- 0.4.0
* Adding ignore_fail argument by @AmploDev.
* Sending all available streams into selected sink, after successfull connection by @AmploDev.
- 0.3.3
* Updating default sink before turning to ``off`` profile.
- 0.3.2
* Waiting a bit: ``-w/--wait`` before connecting again.
- 0.3.0
* Adding -p / --profile option for using the same script to switch between headset and A2DP audio profiles
- 0.2.5
* Mentioning [mac] argument.
- 0.2.4
* Removing duplicated devices in select device list.
- 0.2.3
* Matching ANSI escape characters. Tested on 16.10 & 16.04
- 0.2.2
* Some sort of code enhancements.
- 0.2.0
* Adding `-V/--version`, `-w/--wait` and `-t/--tries` CLI arguments.
- 0.1.1
* Supporting the `[NEW]` prefix for devices & controllers as advised by @wdullaer
* Drying the code.
"""
import sys
import re
import asyncio
import subprocess as sb
import argparse
__version__ = '0.5.2'
HEX_DIGIT_PATTERN = '[0-9A-F]'
HEX_BYTE_PATTERN = '%s{2}' % HEX_DIGIT_PATTERN
MAC_ADDRESS_PATTERN = ':'.join((HEX_BYTE_PATTERN, ) * 6)
DEVICE_PATTERN = re.compile('^(?:.*\s)?Device\s(?P<mac>%s)\s(?P<name>.*)' % MAC_ADDRESS_PATTERN)
CONTROLLER_PATTERN = re.compile('^(?:.*\s)?Controller\s(?P<mac>%s)\s(?P<name>.*)' % MAC_ADDRESS_PATTERN)
WAIT_TIME = 2.25
TRIES = 15
PROFILE = 'a2dp'
_profiles = {
'a2dp': 'a2dp_sink',
'hsp': 'headset_head_unit',
'off': 'off'
}
# CLI Arguments
parser = argparse.ArgumentParser(prog=sys.argv[0])
parser.add_argument('-e', '--echo', action='store_true', default=False,
help='If given, the subprocess stdout will be also printed on stdout.')
parser.add_argument('-w', '--wait', default=WAIT_TIME, type=float,
help='The seconds to wait for subprocess output, default is: %s' % WAIT_TIME)
parser.add_argument('-t', '--tries', default=TRIES, type=int,
help='The number of tries if subprocess is failed. default is: %s' % TRIES)
parser.add_argument('-p', '--profile', default=PROFILE,
help='The profile to switch to. available options are: hsp, a2dp. default is: %s' % PROFILE)
parser.add_argument('-V', '--version', action='store_true', help='Show the version.')
parser.add_argument('mac', nargs='?', default=None)
# Exceptions
class SubprocessError(Exception):
pass
class RetryExceededError(Exception):
pass
class BluetoothctlProtocol(asyncio.SubprocessProtocol):
def __init__(self, exit_future, echo=True):
self.exit_future = exit_future
self.transport = None
self.output = None
self.echo = echo
def listen_output(self):
self.output = ''
def not_listen_output(self):
self.output = None
def pipe_data_received(self, fd, raw):
d = raw.decode()
if self.echo:
print(d, end='')
if self.output is not None:
self.output += d
def process_exited(self):
self.exit_future.set_result(True)
def connection_made(self, transport):
self.transport = transport
print('Connection MADE')
async def send_command(self, c):
stdin_transport = self.transport.get_pipe_transport(0)
# noinspection PyProtectedMember
stdin_transport._pipe.write(('%s\n' % c).encode())
async def search_in_output(self, expression, fail_expression=None):
if self.output is None:
return None
for l in self.output.splitlines():
if fail_expression and re.search(fail_expression, l, re.IGNORECASE):
raise SubprocessError('Expression "%s" failed with fail pattern: "%s"' % (l, fail_expression))
if re.search(expression, l, re.IGNORECASE):
return True
async def send_and_wait(self, cmd, wait_expression, fail_expression='fail'):
try:
self.listen_output()
await self.send_command(cmd)
while not await self.search_in_output(wait_expression.lower(), fail_expression=fail_expression):
await wait()
finally:
self.not_listen_output()
async def disconnect(self, mac):
print('Disconnecting the device.')
await self.send_and_wait('disconnect %s' % ':'.join(mac), 'Successful disconnected')
async def connect(self, mac):
print('Connecting again.')
await self.send_and_wait('connect %s' % ':'.join(mac), 'Connection successful')
async def trust(self, mac):
await self.send_and_wait('trust %s' % ':'.join(mac), 'trust succeeded')
async def quit(self):
await self.send_command('quit')
async def get_list(self, command, pattern):
result = set()
try:
self.listen_output()
await self.send_command(command)
await wait()
for l in self.output.splitlines():
m = pattern.match(l)
if m:
result.add(m.groups())
return sorted(list(result), key=lambda i: i[1])
finally:
self.not_listen_output()
async def list_devices(self):
return await self.get_list('devices', DEVICE_PATTERN)
async def list_paired_devices(self):
return await self.get_list('paired-devices', DEVICE_PATTERN)
async def list_controllers(self):
return await self.get_list('list', CONTROLLER_PATTERN)
async def select_paired_device(self):
print('Selecting device:')
devices = await self.list_paired_devices()
count = len(devices)
if count < 1:
raise SubprocessError('There is no connected device.')
elif count == 1:
return devices[0]
for i, d in enumerate(devices):
print('%d. %s %s' % (i+1, d[0], d[1]))
print('Select device[1]:')
selected = input()
return devices[0 if not selected.strip() else (int(selected) - 1)]
async def wait(delay=None):
return await asyncio.sleep(WAIT_TIME or delay)
async def execute_command(cmd, ignore_fail=False):
p = await asyncio.create_subprocess_shell(cmd, stdout=sb.PIPE, stderr=sb.PIPE)
stdout, stderr = await p.communicate()
stdout, stderr = \
stdout.decode() if stdout is not None else '', \
stderr.decode() if stderr is not None else ''
if p.returncode != 0 or stderr.strip() != '':
message = 'Command: %s failed with status: %s\nstderr: %s' % (cmd, p.returncode, stderr)
if ignore_fail:
print('Ignoring: %s' % message)
else:
raise SubprocessError(message)
return stdout
async def execute_find(cmd, pattern, tries=0, fail_safe=False):
tries = tries or TRIES
message = 'Cannot find `%s` using `%s`.' % (pattern, cmd)
retry_message = message + ' Retrying %d more times'
while True:
stdout = await execute_command(cmd)
match = re.search(pattern, stdout)
if match:
return match.group()
elif tries > 0:
await wait()
print(retry_message % tries)
tries -= 1
continue
if fail_safe:
return None
raise RetryExceededError('Retry times exceeded: %s' % message)
async def find_dev_id(mac, **kw):
return await execute_find('pactl list cards short', 'bluez_card.%s' % '_'.join(mac), **kw)
async def find_sink(mac, **kw):
return await execute_find('pacmd list-sinks', 'bluez_sink.%s' % '_'.join(mac), **kw)
async def set_profile(device_id, profile):
print('Setting the %s profile' % profile)
try:
return await execute_command('pactl set-card-profile %s %s' % (device_id, _profiles[profile]))
except KeyError:
print('Invalid profile: %s, please select one one of a2dp or hsp.' % profile, file=sys.stderr)
raise SystemExit(1)
async def set_default_sink(sink):
print('Updating default sink to %s' % sink)
return await execute_command('pacmd set-default-sink %s' % sink)
async def move_streams_to_sink(sink):
streams = await execute_command('pacmd list-sink-inputs | grep "index:"', True)
for i in streams.split():
i = ''.join(n for n in i if n.isdigit())
if i != '':
print('Moving stream %s to sink' % i)
await execute_command('pacmd move-sink-input %s %s' % (i, sink))
return sink
async def main(args):
global WAIT_TIME, TRIES
if args.version:
print(__version__)
return 0
mac = args.mac
# Hacking, Changing the constants!
WAIT_TIME = args.wait
TRIES = args.tries
exit_future = asyncio.Future()
transport, protocol = await asyncio.get_event_loop().subprocess_exec(
lambda: BluetoothctlProtocol(exit_future, echo=args.echo), 'bluetoothctl'
)
try:
if mac is None:
mac, _ = await protocol.select_paired_device()
mac = mac.split(':' if ':' in mac else '_')
print('Device MAC: %s' % ':'.join(mac))
device_id = await find_dev_id(mac, fail_safe=True)
if device_id is None:
print('It seems device: %s is not connected yet, trying to connect.' % ':'.join(mac))
await protocol.trust(mac)
await protocol.connect(mac)
device_id = await find_dev_id(mac)
sink = await find_sink(mac, fail_safe=True)
if sink is None:
await set_profile(device_id, args.profile)
sink = await find_sink(mac)
print('Device ID: %s' % device_id)
print('Sink: %s' % sink)
await set_default_sink(sink)
await wait()
await set_profile(device_id, 'off')
if args.profile is 'a2dp':
await protocol.disconnect(mac)
await wait()
await protocol.connect(mac)
device_id = await find_dev_id(mac)
print('Device ID: %s' % device_id)
await wait(2)
await set_profile(device_id, args.profile)
await set_default_sink(sink)
await move_streams_to_sink(sink)
except (SubprocessError, RetryExceededError) as ex:
print(str(ex), file=sys.stderr)
return 1
finally:
print('Exiting bluetoothctl')
await protocol.quit()
await exit_future
# Close the stdout pipe
transport.close()
if args.profile == 'a2dp':
print('"Enjoy" the HiFi stereo music :)')
else:
print('"Enjoy" your headset audio :)')
if __name__ == '__main__':
sys.exit(asyncio.get_event_loop().run_until_complete(main(parser.parse_args())))
#!/usr/bin/python
import dbus
from dbus.mainloop.glib import DBusGMainLoop
import gobject
import subprocess
import time
# Where you keep the above script. Must be executable, obviously.
A2DP = '/home/mihara/.local/bin/a2dp'
# A list of device IDs that you wish to run it on.
DEV_IDS = ['00:18:09:30:FC:D8','FC:58:FA:B1:2D:25']
device_paths = []
devices = []
dbus_loop = DBusGMainLoop()
bus = dbus.SystemBus(mainloop=dbus_loop)
def generate_handler(device_id):
last_ran = [0] # WHOA, this is crazy closure behavior: A simple variable does NOT work.
def cb(*args, **kwargs):
if args[0] == 'org.bluez.MediaControl1':
if args[1].get('Connected'):
print("Connected {}".format(device_id))
if last_ran[0] < time.time() - 120:
print("Fixing...")
subprocess.call([A2DP,device_id])
last_ran[0] = time.time()
else:
print("Disconnected {}".format(device_id))
return cb
# Figure out the path to the headset
man = bus.get_object('org.bluez', '/')
iface = dbus.Interface(man, 'org.freedesktop.DBus.ObjectManager')
for device in iface.GetManagedObjects().keys():
for id in DEV_IDS:
if device.endswith(id.replace(':','_')):
device_paths.append((id, device))
for id, device in device_paths:
headset = bus.get_object('org.bluez', device)
headset.connect_to_signal("PropertiesChanged", generate_handler(id), dbus_interface='org.freedesktop.DBus.Properties')
devices.append(headset)
loop = gobject.MainLoop()
loop.run()
[Unit]
Description=Fix BT Speaker
[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/fix-bt.py
[Install]
WantedBy=multi-user.target
@ragusa87

This comment has been minimized.

ragusa87 commented Sep 12, 2016

Thanks @pylover, you made my day!

@DeathCamel57

This comment has been minimized.

DeathCamel57 commented Sep 16, 2016

Thank you so, so much!

@herrfz

This comment has been minimized.

herrfz commented Sep 25, 2016

Without the main function this could actually be an async/await-based library for interacting with bluetoothctl ... brilliant!

@tavurth

This comment has been minimized.

tavurth commented Sep 29, 2016

Great script, fixed my issue for bluetooth headphones MDR-ZX770BT. Thanks @pylover!

@theweirdguy

This comment has been minimized.

theweirdguy commented Oct 1, 2016

Man u are a savior, thanks bro #<3 python

@dginev

This comment has been minimized.

dginev commented Oct 4, 2016

Thanks @pylover this gist is a major convenience boost for me. Do you have any advice who/where to ping to resolve the mainline issue?

@RobRobM

This comment has been minimized.

RobRobM commented Oct 21, 2016

sorry, still not working:

./bin/a2dp.py 
Connection MADE
Selecting device:
There is no connected device.
Exiting bluetoothctl
bluetoothctl 
[NEW] Controller 5C:F3:70:68:D9:1B desktop [default]
[NEW] Device FC:F1:52:1E:DB:99 MDR-ZX750BN
[bluetooth]# 
@pylover

This comment has been minimized.

Owner

pylover commented Oct 21, 2016

@RobbieFM: Please pair and connect your headset before running this script.

@RobRobM

This comment has been minimized.

RobRobM commented Oct 22, 2016

The Sony headset MDR-ZX750BN was paired and connected. I can switch to a2dp by first settings the profile to HSP/Hfp profile, then disconnect and reconnect via blueman, then I can switch to a2dp. I was hoping your script would automate this.

If you need me to test anything, just let me know.

I have a Sony headset MDR-ZX750BN and Sony speaker Srs-x33 to test with.

@pylover

This comment has been minimized.

Owner

pylover commented Oct 22, 2016

@RobbieFM, Interesting, thanks a lot for feedback.

I have no ubuntu 16.10 installed, so wait to i download, install & test it.

@pylover

This comment has been minimized.

Owner

pylover commented Oct 23, 2016

@RobbieFM, it's fixed on 0.2.3.

Thanks a lot for reporting.

@RobRobM

This comment has been minimized.

RobRobM commented Oct 24, 2016

@pylover, sorry still not working. I do get new errors:

Connection MADE
Selecting device:
Device MAC: FC:F1:52:1E:DB:99
Cannot find bluez_sink.FC_F1_52_1E_DB_99 using pacmd list-sinks. Retrying 8 more times
Cannot find bluez_sink.FC_F1_52_1E_DB_99 using pacmd list-sinks. Retrying 7 more times
Cannot find bluez_sink.FC_F1_52_1E_DB_99 using pacmd list-sinks. Retrying 6 more times
Cannot find bluez_sink.FC_F1_52_1E_DB_99 using pacmd list-sinks. Retrying 5 more times
Cannot find bluez_sink.FC_F1_52_1E_DB_99 using pacmd list-sinks. Retrying 4 more times
Cannot find bluez_sink.FC_F1_52_1E_DB_99 using pacmd list-sinks. Retrying 3 more times
Cannot find bluez_sink.FC_F1_52_1E_DB_99 using pacmd list-sinks. Retrying 2 more times
Cannot find bluez_sink.FC_F1_52_1E_DB_99 using pacmd list-sinks. Retrying 1 more times
Command: pactl set-card-profile bluez_card.FC_F1_52_1E_DB_99 a2dp_sink failed with status: 1
stderr: Mislukt: Invoer-/Uitvoerfout (I/O)

Exiting bluetoothctl

Ps. thanks for your very quick responses

@pylover

This comment has been minimized.

Owner

pylover commented Oct 24, 2016

It's may be something else, here is working well on ubuntu 16.10.

So, ensure the headset is shown on sound control. increasing wait time -w and tries -t. may help.

Run pacmd list-sinks manually to ensure the sink is created.

@romanovzky

This comment has been minimized.

romanovzky commented Nov 8, 2016

You can't imagine how much I appreciate this! Thanks a lot mate, big help!

@pylover

This comment has been minimized.

Owner

pylover commented Nov 8, 2016

@romanovzky, Thanks for comment.

@MoonDogg

This comment has been minimized.

MoonDogg commented Nov 9, 2016

I can confirm this working on GalliumOS 2.0 (Xubuntu 16.04) on a Acer C720. Thanks!! My only issue is that I have if I disconnect my headset and reconnect I have to delete it and re-pair and rerun script for it to work again. Any suggestions?

@pylover

This comment has been minimized.

Owner

pylover commented Nov 9, 2016

@MoonDogg, This script is written to prevent manualy turning off, and connection again. so if it's not working, as well as i told you. there are some problems.

@MoonDogg

This comment has been minimized.

MoonDogg commented Nov 9, 2016

I think it was a miss communication... it works... I meant after I turn my device off and then go to reconnect it later... it connects but not working in A2PD if I re-run the script it works fine. Is there a way to make the script run automatically every time I connect a A2DP device?

@AJMaxwell

This comment has been minimized.

AJMaxwell commented Nov 9, 2016

I get the following error when running this...

`python3.5 a2dp.py
Connection MADE
Selecting device:
Device MAC: 00:02:5B:00:2E:43
Device ID: bluez_card.00_02_5B_00_2E_43
Sink: bluez_sink.00_02_5B_00_2E_43
Turning off audio profile.
Disconnecting the device.
Connecting again.
Setting A2DP profile
Device ID: bluez_card.00_02_5B_00_2E_43
Command: pactl set-card-profile bluez_card.00_02_5B_00_2E_43 a2dp_sink failed with status: 1
stderr: Failure: Input/Output error

Exiting bluetoothctl`

@pylover

This comment has been minimized.

Owner

pylover commented Nov 10, 2016

@AJMaxwell

It relates to pactl set-card-profile bluez_card.00_02_5B_00_2E_43 a2dp_sink, run and troubleshoot it manualy.

Make sure to another bluetooth device is not trying to connect to your host.
Try to get working HSP/HFP profile. before running this script.

This script will only works if your headset is acting well on HSP/HFP profile.

@pylover

This comment has been minimized.

Owner

pylover commented Nov 10, 2016

@MoonDogg, Good idea, but how ?

@MoonDogg

This comment has been minimized.

MoonDogg commented Nov 10, 2016

@pylover, I was hoping you would know. :P I am going to do some research see if I can figure it out. I will let you know what I find.

@MoonDogg

This comment has been minimized.

MoonDogg commented Nov 10, 2016

@pylover, I found this.. http://askubuntu.com/questions/138522/how-do-i-run-a-script-when-a-bluetooth-device-connects and it uses this script ( https://github.com/yxlao/bluetooth-runner ) when a blue-tooth device connects.. I just don't know the syntax to edit it to call your script. Maybe you could modify it?

@pylover

This comment has been minimized.

Owner

pylover commented Nov 10, 2016

@MoonDogg, Thanks a lot

@agorelick

This comment has been minimized.

agorelick commented Nov 12, 2016

Thank you! This is great, works flawlessly :)

@MoonDogg

This comment has been minimized.

MoonDogg commented Nov 14, 2016

@pylover are you editing the script I linked to call your script or are you incorporating it into your script? Either way let me know and I can test it out for you. Thanks for you help.

@pylover

This comment has been minimized.

Owner

pylover commented Nov 14, 2016

@MoonDogg, i'll do it in the next a few days . thanks a lot for contribution

@acidreign

This comment has been minimized.

acidreign commented Nov 18, 2016

This script saves me from a giant headache I was suffering through on a daily basis. You are THE MAN @pylover!!

@edelreal

This comment has been minimized.

edelreal commented Nov 25, 2016

Wonderful workaround, thanks!

I've posted details of my debugging attempts to https://bugs.freedesktop.org/show_bug.cgi?id=92102 ;
perhaps it is something pulseaudio can learn to handle (unless this is a bug that needs to fixed in bluez instead).

@bobberkarl

This comment has been minimized.

bobberkarl commented Dec 1, 2016

3 years of Connecting/deconnecting were just solved. Thank you @pylover .

@ghost

This comment has been minimized.

ghost commented Dec 6, 2016

Kind thanks for this script kind sirs! You have saved my from six month struggle with bluetooth on my laptop!

@VirToReal

This comment has been minimized.

VirToReal commented Dec 8, 2016

Thanks Sir Awesome!

@peteruithoven

This comment has been minimized.

peteruithoven commented Dec 8, 2016

One great addition would be using pactl subsribe to automatically perform this when a device is connected.

@pylover

This comment has been minimized.

Owner

pylover commented Dec 8, 2016

@peteruithoven thanks a lot

@jomarocas

This comment has been minimized.

jomarocas commented Dec 12, 2016

Hi look at

> jonathan@jonathan-desktop:~/d68be364adac5f946887b85e6ed6e7ae$ python a2dp.py 
>   File "a2dp.py", line 118
>     print(d, end='')
>                 ^
> SyntaxError: invalid syntax

how can fix this

@pylover

This comment has been minimized.

Owner

pylover commented Dec 12, 2016

Use python 3.5 or higher

@jomarocas

This comment has been minimized.

jomarocas commented Dec 13, 2016

ok thanks for use python 3.5

nano ~/.bashrc
alias python=python3.5
source ~/.bashrc

and working

@acetheplace

This comment has been minimized.

acetheplace commented Dec 15, 2016

hi pylover

also getting
Setting A2DP profile Device ID: bluez_card.0C_E0_E4_6B_48_AB Command: pactl set-card-profile bluez_card.0C_E0_E4_6B_48_AB a2dp_sink failed with status: 1 stderr: Failure: Input/Output error
my bluetooth device when connected:
https://dpaste.de/bZk6

any ideas?

@pylover

This comment has been minimized.

Owner

pylover commented Dec 16, 2016

Your headset is not connected well as well. check /var/log/syslog

@frankitox

This comment has been minimized.

frankitox commented Dec 18, 2016

Maaaan! Thanks!!! I wasted my whole afternoon with this shit. God. Just a chmod u+x a2dp.py && mv a2dp.py ~/.bin/speakers and everything's good to go. Working on Lubuntu 16.04.1.

@ytian801

This comment has been minimized.

ytian801 commented Dec 19, 2016

Works great in ubuntu 16.04.1, thank you so much

@ericek111

This comment has been minimized.

ericek111 commented Dec 19, 2016

Thanks, worked perfectly!

@opennomad

This comment has been minimized.

opennomad commented Dec 19, 2016

Awesome. Works like a charm. Thank you!

@rigtersys

This comment has been minimized.

rigtersys commented Jan 6, 2017

Thanks a lot! Confirmed working on Linux Mint Sarah (is also ubuntu 16.04.1but you never know)

@oofnikj

This comment has been minimized.

oofnikj commented Jan 9, 2017

Hi,
Very cool script, thanks 👍
I had a problem where my device connected automatically with 'off' profile, which meant that it did not appear in pacmd list-sinks.
So I modified the code a little bit to connect in HSP/HFP mode and then look for the sink again.
Now it works flawlessly. I launch the script with a keyboard shortcut when I turn on my speakers.
Running Xubuntu 16.04.
Cheers

@wayne003

This comment has been minimized.

wayne003 commented Jan 9, 2017

Life saver!

@yamatban

This comment has been minimized.

yamatban commented Jan 14, 2017

has anyone gotten the mic in the bluetooth headphones to work the system sees it but gets no input

@chocolatecharme

This comment has been minimized.

chocolatecharme commented Jan 17, 2017

Thank you so much!

@vmavromatis

This comment has been minimized.

vmavromatis commented Jan 18, 2017

Works like a charm @ Lubuntu 16.10. Thanks!
I tried @moondog's suggestion for bluetooth-runner but unfortunately that script does not detect my bluetooth connection....

@doktrjones

This comment has been minimized.

doktrjones commented Jan 20, 2017

Worked perfectly first time but since reboot I keep getting the following error:
Command: pactl set-card-profile bluez_card.00_18_09_9B_EA_2F a2dp_sink failed with status: 1 stderr: Failure: Input/Output error
I tried setting a wait time to no avail, any other suggestions?

@peteruithoven

This comment has been minimized.

peteruithoven commented Jan 21, 2017

I have a Bose Quietcontrol 30, switched laptops (now on a XPS15) (both ran Elementary Loki, based on Ubuntu 16.04), and it all worked fine for a while but after some updates the device stopped showing up as sound output. So I tried this script (thanks for sharing!) and just like @RobbieFM now I'm getting the following error:

Cannot find `bluez_card.04_52_C7_1B_7B_BC` using `pactl list cards short`. Retrying 8 more times

The device isn't showing up in the sound output's list, but that was the problem that I hoped to fix with this script. I don't see the device in the pacmd list-sinks list.

I tried the following suggestion: http://askubuntu.com/a/865214/519324

I tried pactl list short | grep bluetooth, but I'm not sure whether it's result is a good sign.

$ pactl list short | grep bluetooth
7	module-bluetooth-policy		
22	module-bluetooth-discover

I'll try to do some more research, but tips are welcome.

I found http://askubuntu.com/a/836202/519324 but Audio Profile didn't have a submenu. So I tried a couple of things, like pressing the Setup button and selecting Connect to: Handsfree. Suddenly it did appear in the Sounds output menu and the Audio Profile menu has options.
I also noticed that earlier my laptop appeared without a name in the bluetooth connects and now the proper name is shown.
Looks solved... No clue why or what went wrong.

@peteruithoven

This comment has been minimized.

peteruithoven commented Jan 22, 2017

I can't edit my previous comment anymore, so I'll continue in a new one.
Using blueman to connect to my bluetooth device as handsfree doesn't seem to be a permanent fix, I have to do it after every reboot. I did notice that when I reboot my bluetooth device I get the following notification:

Bluetooth Authentication
Authorization request for: Bose QuietControl 30 (04:52:C7:1B:7B:BC)
Service: Advanced Audio

And a reboot later it works great, I only need to reboot the bluetooth device.

@pylover

This comment has been minimized.

Owner

pylover commented Jan 22, 2017

@peteruithoven, yes you have to reboot your headset also

@Mihara

This comment has been minimized.

Mihara commented Jan 26, 2017

In case it might be helpful to someone, here's how I invoke a2dp.py automatically in Ubuntu 16.04, only for specific headsets:

#!/usr/bin/python

import dbus
from dbus.mainloop.glib import DBusGMainLoop
import gobject

import subprocess
import time

# Where you keep the above script. Must be executable, obviously.
A2DP = '/home/mihara/.local/bin/a2dp'

# A list of device IDs that you wish to run it on.
DEV_IDS = ['00:18:09:30:FC:D8','FC:58:FA:B1:2D:25']

device_paths = []
devices = []

dbus_loop = DBusGMainLoop()
bus = dbus.SystemBus(mainloop=dbus_loop)

def generate_handler(device_id):

    last_ran = [0] # WHOA, this is crazy closure behavior: A simple variable does NOT work.

    def cb(*args, **kwargs):
        if args[0] == 'org.bluez.MediaControl1':
            if args[1].get('Connected'):
                print("Connected {}".format(device_id))
                if last_ran[0] < time.time() - 120:
                    print("Fixing...")
                    subprocess.call([A2DP,device_id])
                    last_ran[0] = time.time()
            else:
                print("Disconnected {}".format(device_id))

    return cb

# Figure out the path to the headset
man = bus.get_object('org.bluez', '/')
iface = dbus.Interface(man, 'org.freedesktop.DBus.ObjectManager')
for device in iface.GetManagedObjects().keys():
    for id in DEV_IDS:
        if device.endswith(id.replace(':','_')):
            device_paths.append((id, device))

for id, device in device_paths:
    headset = bus.get_object('org.bluez', device)
    headset.connect_to_signal("PropertiesChanged", generate_handler(id), dbus_interface='org.freedesktop.DBus.Properties')
    devices.append(headset)

loop = gobject.MainLoop()
loop.run()

Usage: Put the device IDs of your A2DP devices into DEV_IDS and the full path of your a2dp.py into the A2DP variable. Run from Startup Applications. Whenever you connect an audio device listed, a2dp.py will be automatically run shortly to sort it out. This will not work on earlier versions of Ubuntu, because it depends on the DBus API flavor introduced in Bluez 5.

@pylover

This comment has been minimized.

Owner

pylover commented Jan 28, 2017

@Mihara, Thank's a lot.

@tombrereton

This comment has been minimized.

tombrereton commented Feb 11, 2017

@pylover holy shit, thank you for saving my sanity

@ZlatanStanojevic

This comment has been minimized.

ZlatanStanojevic commented Feb 16, 2017

You guys are my personal heroes!

@feryardiant

This comment has been minimized.

feryardiant commented Feb 20, 2017

Hi @pylover

Thanks for the script, but unfortunately I got this.

Connection MADE
Selecting device:
1. 00:01:01:00:02:DF AWEI-A980BL
2. AC:C1:EE:0D:9B:EA Mimin-R4
Select device[1]:
1
Device MAC: 00:01:01:00:02:DF
Cannot find `bluez_sink.00_01_01_00_02_DF` using `pacmd list-sinks`. Retrying 8 more times
Cannot find `bluez_sink.00_01_01_00_02_DF` using `pacmd list-sinks`. Retrying 7 more times
Cannot find `bluez_sink.00_01_01_00_02_DF` using `pacmd list-sinks`. Retrying 6 more times
Cannot find `bluez_sink.00_01_01_00_02_DF` using `pacmd list-sinks`. Retrying 5 more times
Cannot find `bluez_sink.00_01_01_00_02_DF` using `pacmd list-sinks`. Retrying 4 more times
Cannot find `bluez_sink.00_01_01_00_02_DF` using `pacmd list-sinks`. Retrying 3 more times
Cannot find `bluez_sink.00_01_01_00_02_DF` using `pacmd list-sinks`. Retrying 2 more times
Cannot find `bluez_sink.00_01_01_00_02_DF` using `pacmd list-sinks`. Retrying 1 more times
Device ID: bluez_card.00_01_01_00_02_DF
Sink: bluez_sink.00_01_01_00_02_DF
Turning off audio profile.
Disconnecting the device.
Connecting again.
Expression "Failed to connect: org.bluez.Error.Failed" failed with fail pattern: "fail"
Exiting bluetoothctl

Any idea?

@AJMaxwell

This comment has been minimized.

AJMaxwell commented Feb 22, 2017

Update: I had to put my headphones into "pairing" mode before running the script. After that, the script ran successfully.

@jvalenciag

This comment has been minimized.

jvalenciag commented Feb 25, 2017

Worked very well on ubuntu 16.10, thank's a lot!

@RajatArora08

This comment has been minimized.

RajatArora08 commented Mar 3, 2017

Perfect! Problem solved! Thanks a ton @pylover

@jtryan

This comment has been minimized.

jtryan commented Mar 5, 2017

@pylover Fantastic work! Using it on 16.10. Now I can get off fedora. Thanks

@pylover

This comment has been minimized.

Owner

pylover commented Mar 5, 2017

@jtryan, Good for you, get rid of yum and a lot of old and crappy tools.

@IzzySoft

This comment has been minimized.

IzzySoft commented Mar 6, 2017

@feryardiant Yes. I just had the same, easy to fix. First, the bunch of "cannot find" can be ignored. The trouble is the connect command being issued before the device is ready, it needs a little sleep.

@pylover easy fix:

from time import sleep
…
print('Connecting again.')
sleep(2)
await protocol.connect(mac)

Did the job for me. Comment: importing sleep from time, then right after the print('Connecting again.') line add a sleep for 2 secs. Shouldn't hurt too much for those not needing it – but helps a lot for those who do 😺

@pylover

This comment has been minimized.

Owner

pylover commented Mar 6, 2017

@IzzySoft, This an asyncio script, You should not use time.sleep, because it will block the all async tasks. So, it's good to use asyncio.sleep function instead. And making a CLI argument for sleep time is appreciated.

@DominicWatson

This comment has been minimized.

DominicWatson commented Mar 7, 2017

This is so good, thank you! I made some additions in my own fork to be able to specify which profile to use. This way I can add a -p headset_head_unit argument and use all the same logic to switch to headset mode for calls. I realize there's probably a separate script that could do this without as much code, but figured it made sense to re-use all the good logic you had around setting the output device, etc.

https://gist.github.com/DominicWatson/b1ea0563df1dcdad858926edca3cee01

For me, I then bind two keyboard shortcuts to the two commands:

python3.5 /path/to/a2dp.py -p a2dp_sink
python3.5 /path/to/a2dp.py -p headset_head_unit

# I should probably rename the script to something else given that its now not exclusively about a2dp, but meh

Again, thanks for the script. Fracking awesome. License is a bit restrictive though.

p.s. do you have a git tip / amazon wishlist or somesuch? would love to repay you.

@pylover

This comment has been minimized.

Owner

pylover commented Mar 8, 2017

@DominicWatson, I'm very appreciate you comment. But i'm living at a country which the banking operations with the world are embargoed.
So, please find a homeless around your area and make him/her happy,

@pylover

This comment has been minimized.

Owner

pylover commented Mar 9, 2017

@DominicWatson, I merged your fork, thank's a lot for contributing. The -p/--profile is very useful.

@IzzySoft

This comment has been minimized.

IzzySoft commented Mar 12, 2017

@pylover

This an asyncio script, You should not use time.sleep, because it will block the all async tasks. So, it's good to use asyncio.sleep function instead. And making a CLI argument for sleep time is appreciated.

Sure! Mine was just a "quick hack". I use Python myself here and there, but am not that deep into it – especially not into asyncio, which I admittedly never used myself. Thanks for picking up my "dirty fix" and converting it into a "clean update" 😸

@DominicWatson Very useful addition! I was asking myself for that as well. Glad I now can simply use it!

@dabrovnijk

This comment has been minimized.

dabrovnijk commented Mar 12, 2017

Thanks alot for the script, pylover for the a2dp.py and Mihara for the autostart script.

I have made this fix-bt.service as below. It will run the first time if I disconnect and reconnect my bluetooth speaker and it will fix a2dp_sink properly but it will only run one time and then I will have to restart the service.

fix-bt.py contains the script that Mihara have addded above.

But I do not know if I have made something wrong in my .service or if I should change something in Miharas script (besides what is already told to change in the script).

Should I use Type=dbus? But I do not know what I should write at BusName=, or how I will find that out.

[Unit]
Description=Fix BT Speaker

[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/fix-bt.py

[Install]
WantedBy=multi-user.target
@pylover

This comment has been minimized.

Owner

pylover commented Mar 12, 2017

@dabrovnijk, Interesting, the systemd service is great!

@dabrovnijk

This comment has been minimized.

dabrovnijk commented Mar 12, 2017

Apperantly it seems to work I just had to be patient. When I tested again after couple of minutes it did what it was supposed to do.

I am running this on a headless Ubuntu 16.10 with pulseaudio running as system. I had to replace pacmd with pactl in your original script as pacmd did not find the pulseaudio daemon.

I love this output, and I can't praise you enough. +1:

mar 12 23:48:58 kompakt fix-bt.py[1107]: Device ID: bluez_card.00_02_3C_5E_28_91
mar 12 23:48:58 kompakt fix-bt.py[1107]: Sink: bluez_sink.00_02_3C_5E_28_91
mar 12 23:48:58 kompakt fix-bt.py[1107]: Turning off audio profile.
mar 12 23:48:58 kompakt fix-bt.py[1107]: Disconnecting the device.
mar 12 23:48:58 kompakt fix-bt.py[1107]: Connecting again.
mar 12 23:48:58 kompakt fix-bt.py[1107]: Device ID: bluez_card.00_02_3C_5E_28_91
mar 12 23:48:58 kompakt fix-bt.py[1107]: Setting the a2dp profile
mar 12 23:48:58 kompakt fix-bt.py[1107]: Updating default sink
mar 12 23:48:58 kompakt fix-bt.py[1107]: Exiting bluetoothctl
mar 12 23:48:58 kompakt fix-bt.py[1107]: "Enjoy" the HiFi stereo music :)

l

@pylover

This comment has been minimized.

Owner

pylover commented Mar 13, 2017

@dabrovnijk, It's OK, replace and test it.

@pylover

This comment has been minimized.

Owner

pylover commented Mar 18, 2017

@ktatts, You have to pair/connect you headphones before running this script.

@guyviner

This comment has been minimized.

guyviner commented Mar 24, 2017

NOOB/NOVICE/EASY/HOW TO INSTALL INSTRUCTIONS:

  1. Click 'Download Zip' at the top right corner of this page
  2. Save to desktop, choose a short name for it, like 'bluetooth'
  3. Go to your desktop
  4. Right click the file you just downloaded
  5. Choose 'Extract Here'
    5b) Pair your bluetooth headset.
  6. Open your terminal
  7. Navigate to the file by typing cd ~/Desktop/bluetooth
  8. Type python3.5 a2dp.py
  9. Choose the headset from the list, if a list pops up
  10. Done! Enjoy.

Thanks so much to @pylover for this script.

**Update. My guide may or may not be correct, because the script hasn't worked for me. I still need to connect twice to get A2DP to work. Can someone review my guide to see what I could have done wrong?

@FrostyPatronus

This comment has been minimized.

FrostyPatronus commented Mar 27, 2017

Thank you very much! Works perfectly for Ubuntu 16.04

@neylsongularte

This comment has been minimized.

neylsongularte commented Apr 8, 2017

thanks! Work on Elementary OS 0.4

@hamedaakhlaghi

This comment has been minimized.

hamedaakhlaghi commented Apr 9, 2017

Thanks. It's worked perfect, I'm proud of you.

@AndreyShprengel

This comment has been minimized.

AndreyShprengel commented Apr 10, 2017

Awesome thanks!

@selenith

This comment has been minimized.

selenith commented Apr 11, 2017

It Work ! You make my day 👍

@AmploDev

This comment has been minimized.

AmploDev commented Apr 12, 2017

You helped a lot! Hope I can do something back!

I had the problem that the already running audio streams were not being transferred to my bluetooth headphone. I had to manually set the running playback to my headphone in volume control.

I first had to add an ignore_fail to the execute_command to suppress errors when there are no currently playing audio streams (so the execute_command doesn't find anything, which is no problem here).

async def execute_command(cmd, ignore_fail=False):
    p = await asyncio.create_subprocess_shell(cmd, stdout=sb.PIPE, stderr=sb.PIPE)
    stdout, stderr = await p.communicate()
    stdout, stderr = \
        stdout.decode() if stdout is not None else '', \
        stderr.decode() if stderr is not None else ''
    if ignore_fail == False and (p.returncode != 0 or stderr.strip() != ''):
        raise SubprocessError('Command: %s failed with status: %s\nstderr: %s' % (cmd, p.returncode, stderr))
    return stdout

Than I added the following on line 282 to move all streams to the new sink:

async def move_streams_to_sink(sink):
    streams = await execute_command('pacmd list-sink-inputs | grep "index:"', True)    
    for i in streams.split():
        i = ''.join(n for n in i if n.isdigit())
        if i != '':
            print('Moving stream %s to sink' % i)
            await execute_command('pacmd move-sink-input %s %s' % (i, sink))
    return sink

and than on (the now) line 347, after the line with await set_default_sink(sink), I added:

await move_streams_to_sink(sink)

This helps sending all running audio to your headphones. This is my very first python attempt, so I'm sure it can be more effective. Hopes this helpes you guys/girls!

PS: Sorry for all the edits, I'm new to github. I have made a fork to make things easier!

@chaosakos

This comment has been minimized.

chaosakos commented Apr 13, 2017

I love you :)
This is why I love linus.

@marcusbesjes

This comment has been minimized.

marcusbesjes commented Apr 13, 2017

works! awesome!

@flymike

This comment has been minimized.

flymike commented Apr 14, 2017

Thanks to @pylover for the wonderful script, @Mihara for that auto-run daemon and @dabrovnijk for the systemd service file, now my headphone is automatically set whenever I switch it on.

Just wanted to add, if you like it run as a systemd service, you have to set it in the user space, the system-wide service won't work due to pactl can't run as pid 1.

  1. Name @dabrovnijk's systemd service code to fix-bt.service and copy it into /etc/systemd/user

  2. Run
    systemctl --user enable fix-bt.service

  3. Reboot

That's it.

Update:
Found out that the systemd service is always dead after fresh boot up (It does work with manual start), so google around and here is the solution:

[Unit]
Description=Fix BT Speaker

[Service]
Type=simple
Restart=always
ExecStart=/usr/bin/fix-bt.py

[Install]
WantedBy=multi-user.target

Just change the multi-user.target to default.target, then disable/re-enable the service. Now it really works.

[Install]
WantedBy=default.target
@pylover

This comment has been minimized.

Owner

pylover commented Apr 16, 2017

@AmploDev, Your fork is merged. Thanks a lot for enhancement.

@AmploDev

This comment has been minimized.

AmploDev commented Apr 16, 2017

@pylover You're welcome and thank you for your awesome script! I was ready to throw my headphones through my screen till you saved the day!

@bikashg

This comment has been minimized.

bikashg commented Apr 16, 2017

What a wonderful gift! Thanks, man.

@jacopotolja

This comment has been minimized.

jacopotolja commented Apr 27, 2017

I envy all of you because you know how to run a script. I made a file in my home dir i copied the script I made it executable by the properties and execute, nothing happen, sorry bothering you but I need more instructions

@pylover

This comment has been minimized.

Owner

pylover commented Apr 27, 2017

run

chmod +x a2dp.py

then

./a2dp.py
@jvalenciag

This comment has been minimized.

jvalenciag commented May 4, 2017

For people using Ubuntu 17.04 with gnome 3 with the script failing randomly this comment has the solution:

If you think you might have the same issue, first check your pulseaudio instances:

$ ps aux | grep pulseaudio
gdm 2578 4.3 0.1 2350536 12896 ? S<l 15:40 2:10 /usr/bin/pulseaudio --start --log-target=syslog
user 1533 4.3 0.1 2350536 12896 ? S<l 15:48 2:18 /usr/bin/pulseaudio --start --log-target=syslog

If you see a process owned by gdm, you can try to disable pulseaudio spawning by gdm during startup:

$ sudo nano /var/lib/gdm3/.config/pulse/client.conf
autospawn = no
daemon-binary = /bin/true

$ sudo chown gdm:gdm /var/lib/gdm3/.config/pulse/client.conf

It worked for me.

@pylover

This comment has been minimized.

Owner

pylover commented May 6, 2017

@jvalenciag, Thanks a lot.

@HifeFish

This comment has been minimized.

HifeFish commented May 10, 2017

Thank you very much for providing this script. I had been using it manually for a few months, when I decided to automate the process about 10 days ago, unaware that it had already been done. Nevertheless, in case anyone is interested, my successful efforts to accomplish this with a udev rule are documented here: https://askubuntu.com/questions/910501/udev-rule-to-run-python-script/912838#912838

@pylover

This comment has been minimized.

Owner

pylover commented May 11, 2017

@HifeFish, Thanks

@mehdi-gital

This comment has been minimized.

mehdi-gital commented May 18, 2017

Thank you! It worked easy. How did you learn to do this?
I'm a py learner and to me this is amazing.

@pylover

This comment has been minimized.

Owner

pylover commented May 18, 2017

@mehdi-gital, Thanks for completion. I owe the C language.

@pylover

This comment has been minimized.

Owner

pylover commented May 26, 2017

@DrunkCat159, A simple workaround is to turn off and on again your Bluetooth.

@ehzawad

This comment has been minimized.

ehzawad commented Jun 1, 2017

sudo apt-get install pulseaudio-module-bluetooth

I need to run this first as dependencies @pylover

@pylover

This comment has been minimized.

Owner

pylover commented Jun 1, 2017

@ehzawad, Thanks

@MohsenHassani

This comment has been minimized.

MohsenHassani commented Jun 4, 2017

Thanks a lot, PyLover!!!
Getting disappointed for months over my headphone, I suddenly found your script!
It worked like a charm and I couldn't really believe it the moment I heard the A2DP sink quality; lol!
Thanks for sharing, man!

@jkhamilton62

This comment has been minimized.

jkhamilton62 commented Jun 6, 2017

Thank you so much for this great script. I was having to constantly manually restart my bluetooth connection to my CANZ speaker I have setup on my computer running Ubuntu GNOME 16.04 LTS. This is what I did to use the script...

1/. chmod +x a2dp.py
2/. sudo ln -s /path/to/script/a2dp.py /usr/local/bin/a2dp
3/. Added a2dp to my startup applications with the MAC for the CANZ speaker.
4/. Reboot and instant joy!

Thanks again!

@pylover

This comment has been minimized.

Owner

pylover commented Jun 6, 2017

@jkhamilton62, Good trick.

@juaryR

This comment has been minimized.

juaryR commented Jun 20, 2017

Muito Obrigado, uma mão na roda ... Cabo Verde agradece.
thank you, very useful ... Cabo verde thank you so ...

@samhains

This comment has been minimized.

samhains commented Jun 23, 2017

This worked great for me with Bose OE bluetooth headphones/ just make sure that your device is not on the 'Off' profile, and instead place it on the 'Headset Head Unit' profile before running the script.

@pylover

This comment has been minimized.

Owner

pylover commented Jul 5, 2017

@YijunXie, You should wait about 1 minute before running this script, if not working:

Try this:
sudo service bluetooth restart

pulseaudio --kill

Turn off and on bluetooth.

Hope this works.

@mlinfoot

This comment has been minimized.

mlinfoot commented Jul 5, 2017

I cannot thank you and the other contributors enough for this!!! This is the way bluetooth speakers/headsets SHOULD work - simply turn them on.

@pylover

This comment has been minimized.

Owner

pylover commented Jul 8, 2017

@juaryR, você é bem-vindo.

@katp4

This comment has been minimized.

katp4 commented Sep 12, 2017

Thank you so much!!

@Handssolow

This comment has been minimized.

Handssolow commented Oct 21, 2017

Thanks, it runs with python3 (16.04's python 3.5.2). I should have checked the earlier postings about this. I've a Blackstar Tone:Link plugged into a music centre. I've already had very intermittent success. Turning to the script, it seems to depend on the remote bt receiver being listed in pactl list cards short, which mine isn't being, so it go through the 15 times then it tries to connect. If it does connect, as expected the top toolbar bluetooth applet sign shows the lock then this in the terminal-
Setting the off profile
Disconnecting the device.
A second run of the script, the connection is made again but before it could disconnect I'd change the sound settings to A2DP and was playing music (as it is as I type this). Thus in the terminal-
Cannot find bluez_sink.E8_07_BF_21_23_DB using pacmd list-sinks. Retrying 10 more times
Device ID: bluez_card.E8_07_BF_21_23_DB
Sink: bluez_sink.E8_07_BF_21_23_DB
Updating default sink to bluez_sink.E8_07_BF_21_23_DB
Setting the off profile
Disconnecting the device.
Connecting again.
Device ID: bluez_card.E8_07_BF_21_23_DB
Setting the a2dp profile
Updating default sink to bluez_sink.E8_07_BF_21_23_DB
Moving stream 4 to sink
Exiting bluetoothctl
"Enjoy" the HiFi stereo music :)

The script looks good but the absence of my bluetooth adaptor appearing in pacmd list-sinks, looks significant and delaying connections.

@solsticedhiver

This comment has been minimized.

solsticedhiver commented Oct 29, 2017

my MDR-ZX220BT connects on mono HSP/HPF by default. Whatever I tries, nothing changes. So I tried a2dp.py above (changing to python 3.6) and this fixes the issue ! Fine. Thank you

I am using ubuntu 17.10 (I had no problem with 17.04 tough)

@Ranindu

This comment has been minimized.

Ranindu commented Nov 17, 2017

This is the code of life! Brilliant and thank you so much. I've been trying to connect XB950BT for about a week now.

@mahdi13

This comment has been minimized.

mahdi13 commented Nov 25, 2017

Fantastic! Tnx

@pylover

This comment has been minimized.

Owner

pylover commented Nov 25, 2017

You are welcome MR. Perfect !

@mugagambi

This comment has been minimized.

mugagambi commented Nov 28, 2017

@pylover thanks so much.This was giving me headache.Now it works flawlessly.

@BigJayToDaIzo

This comment has been minimized.

BigJayToDaIzo commented Nov 30, 2017

MAN, you just solved a problem in 30 seconds that I've been toying around with for THREE DAYS. You're the man bro! Thank you!

@Hang-Hu

This comment has been minimized.

Hang-Hu commented Dec 1, 2017

Thanks a lot, perfect script!

@gorillaphd

This comment has been minimized.

gorillaphd commented Dec 17, 2017

I managed!! It's amazing, thank you @pylover!!

@elitan

This comment has been minimized.

elitan commented Dec 29, 2017

How did you manage it @gorillaphd?

@rainli323

This comment has been minimized.

rainli323 commented Jan 4, 2018

Thank you!! This is the only thing that worked for me! THANKS!

@AleksueiR

This comment has been minimized.

AleksueiR commented Jan 5, 2018

@oofnikj I have the same problem - headphones connecting with 'off' profile. Could you share your modifications to the script to work around that? Thanks.

@tsonne

This comment has been minimized.

tsonne commented Jan 13, 2018

The original problem still exists on Ubuntu 16.04, but this script fixed my TaoTronics TT-BH09 (finally plays sound now)! THANKS!

@pylover

This comment has been minimized.

Owner

pylover commented Feb 4, 2018

You're running the script by python2. issue python3.5 a2dp.py

@hpeyerl

This comment has been minimized.

hpeyerl commented Mar 5, 2018

I've been annoyed by this problem for months and have wasted hour after hour trying to instrument this. I finally sat down to debug it in earnest and a google of an error message led me to a2dp.py. It 'just worked'. Thanks!

@francesco74

This comment has been minimized.

francesco74 commented Mar 30, 2018

@pylover you are my hero !
Perfect on stretch Raspberry PI 3 !!!

@pylover

This comment has been minimized.

Owner

pylover commented Mar 31, 2018

@francesco74, Interesting, I ❤️ RPI .

@pylover

This comment has been minimized.

Owner

pylover commented Jun 6, 2018

@yochananmarqos: You have to install bluez

@p-himik

This comment has been minimized.

p-himik commented Jun 13, 2018

@pylover FYI, bluez enabled a non-interactive mode for bluetoothctl in 0985a594. Maybe it could be used one day.

@pylover

This comment has been minimized.

Owner

pylover commented Jun 13, 2018

@p-himik, Thanks a lot.

All of this async stuff is just for waiting for an interactive result.

@wanix

This comment has been minimized.

wanix commented Jun 24, 2018

thanks for this script :)

@sandorkazi

This comment has been minimized.

sandorkazi commented Jul 6, 2018

Almost works under Manjaro (Arch) too.

Thanks for the script, though, it was a great help.

@marcusbesjes

This comment has been minimized.

marcusbesjes commented Oct 24, 2018

Almost works under Manjaro (Arch) too.

Thanks for the script, though, it was a great help.

@sandorkazi Did you get it to work on manjaro? I moved from i3buntu to manjaro i3 and it's great except I can't get this to run!
It complains there is no module called dbus (after pip installing something else that it complained about)

 ✘  ~/Applications/fix-bt  > python3.5 fix-bt.py
Traceback (most recent call last):
  File "fix-bt.py", line 3, in <module>
    import dbus
ImportError: No module named 'dbus'

I installed dbus-python but that didn't fix it.

@sherlockhomeless

This comment has been minimized.

sherlockhomeless commented Nov 1, 2018

Works for Ubuntu 18.10 too. Well done!

@adrianmihalko

This comment has been minimized.

adrianmihalko commented Nov 3, 2018

I am trying to make a bluetooth connection on my headless 16.04 server & bluetooth speaker.

@ubuntudev:~$ ./a2dp.py 15:06:04:27:09:4F
Connection MADE
Device MAC: 15:06:04:27:09:4F
Cannot find `bluez_card.15_06_04_27_09_4F` using `pactl list cards short`. Retrying 15 more times
Cannot find `bluez_card.15_06_04_27_09_4F` using `pactl list cards short`. Retrying 14 more times
...
Cannot find `bluez_card.15_06_04_27_09_4F` using `pactl list cards short`. Retrying 3 more times
Cannot find `bluez_card.15_06_04_27_09_4F` using `pactl list cards short`. Retrying 2 more times
Cannot find `bluez_card.15_06_04_27_09_4F` using `pactl list cards short`. Retrying 1 more times
It seems device: 15:06:04:27:09:4F is not connected yet, trying to connect.

I am able to see pair, trust my bluetooth device in bluetoothctl, but system doesn't use it as a soundcard.

@EkanshdeepGupta

This comment has been minimized.

EkanshdeepGupta commented Nov 17, 2018

In case it might be helpful to someone, here's how I invoke a2dp.py automatically in Ubuntu 16.04, only for specific headsets:

#!/usr/bin/python

import dbus
from dbus.mainloop.glib import DBusGMainLoop
import gobject

import subprocess
import time

# Where you keep the above script. Must be executable, obviously.
A2DP = '/home/mihara/.local/bin/a2dp'

# A list of device IDs that you wish to run it on.
DEV_IDS = ['00:18:09:30:FC:D8','FC:58:FA:B1:2D:25']

device_paths = []
devices = []

dbus_loop = DBusGMainLoop()
bus = dbus.SystemBus(mainloop=dbus_loop)

def generate_handler(device_id):

    last_ran = [0] # WHOA, this is crazy closure behavior: A simple variable does NOT work.

    def cb(*args, **kwargs):
        if args[0] == 'org.bluez.MediaControl1':
            if args[1].get('Connected'):
                print("Connected {}".format(device_id))
                if last_ran[0] < time.time() - 120:
                    print("Fixing...")
                    subprocess.call([A2DP,device_id])
                    last_ran[0] = time.time()
            else:
                print("Disconnected {}".format(device_id))

    return cb

# Figure out the path to the headset
man = bus.get_object('org.bluez', '/')
iface = dbus.Interface(man, 'org.freedesktop.DBus.ObjectManager')
for device in iface.GetManagedObjects().keys():
    for id in DEV_IDS:
        if device.endswith(id.replace(':','_')):
            device_paths.append((id, device))

for id, device in device_paths:
    headset = bus.get_object('org.bluez', device)
    headset.connect_to_signal("PropertiesChanged", generate_handler(id), dbus_interface='org.freedesktop.DBus.Properties')
    devices.append(headset)

loop = gobject.MainLoop()
loop.run()

Usage: Put the device IDs of your A2DP devices into DEV_IDS and the full path of your a2dp.py into the A2DP variable. Run from Startup Applications. Whenever you connect an audio device listed, a2dp.py will be automatically run shortly to sort it out. This will not work on earlier versions of Ubuntu, because it depends on the DBus API flavor introduced in Bluez 5.

Hi @Mihara, thanks for the above script. However, upon running it I am getting an error, I'll paste the terminal output below:

ekansh@EkanshInspiron15z ~ $ fix-bt.py
Disconnected 74:5C:4B:61:2E:7E
Connected 74:5C:4B:61:2E:7E
Fixing...
ERROR:dbus.connection:Exception in handler for D-Bus signal:
Traceback (most recent call last):
  File "/usr/lib/python2.7/dist-packages/dbus/connection.py", line 230, in maybe_handle_message
    self._handler(*args, **kwargs)
  File "/home/ekansh/bin/fix-bt.py", line 32, in cb
    subprocess.call([A2DP,device_id])
  File "/usr/lib/python2.7/subprocess.py", line 523, in call
    return Popen(*popenargs, **kwargs).wait()
  File "/usr/lib/python2.7/subprocess.py", line 711, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1343, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory

Upon a google search, it appears that the OSError: [Errno 2] No such file or directory is relatively common, but I couldn't find a precise fix for it. I don't understand the script and it's various components properly, so I'm unable to fix it by myself. Please help.

PS: @py-lover thanks for this awesome script! I have been troubled by this issue for several months now, your script is such a brilliant solution to this problem.

@EkanshdeepGupta

This comment has been minimized.

EkanshdeepGupta commented Nov 17, 2018

Okay, my bad. The error was extremely simple to fix, I had messed up the location of the original a2dp script. I forgot the .py extension in the location of the a2dp script (A2DP = '/home/ekansh/bin/a2dp.py' vs A2DP = '/home/ekansh/bin/a2dp'); once I located the file correctly the script started working flawlessly. Thanks again for the good work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment