2
votes

I'm new to d-bus and bluez. I have a CHIP module, running as a BT speaker. I followed this:

https://github.com/hadess/CHIP-bluetooth-speaker

and also the instructions in the following link to get iPhone volume working:

https://github.com/hadess/CHIP-bluetooth-speaker/issues/8

I'd like to be able to change the volume of the speaker both from my iphone and from the module - so I'm guessing dbus commands is the way to go.

I've successfully connected and disconnected from my iPhone using:

dbus-send --system --print-reply --dest=org.bluez /org/bluez/hci0/dev_A0_D7_95_A9_88_91 org.bluez.Device1.Disconnect

and d-feet was useful to show me how to do it. However, I'm not clear on how I can do play/pause/volumeUp/volumeDown. The documentation here:

https://kernel.googlesource.com/pub/scm/bluetooth/bluez/+/5.43/doc/media-api.txt

explains how to do play/pause but I can't get it to work. I've tried:

dbus-send --system --print-reply --dest=org.bluez /org/bluez/hci0/dev_A0_D7_95_A9_88_91 org.bluez.MediaPlayer1.Pause

and:

dbus-send --system --print-reply --dest=org.bluez /org/bluez/hci0/dev_A0_D7_95_A9_88_91/Player1 org.bluez.MediaPlayer1.Pause

with the same error:

Error org.freedesktop.DBus.Error.UnknownMethod: Method "Pause" with signature "" on interface "org.bluez.MediaPlayer1" doesn't exist

and I'm not at all clear how to change volume (the VolumeUp and VolumeDown commands in mediacontrol1 as shown as deprecated).

Can anyone help?

Update

After great comments from Constantin below (thanks), I'd like some more clarification.

Using d-feet, I get the following information for my device:

enter image description here

should I be able to see a "MediaTransport1" and/or "MediaControl1" interface entry in the listing?

When I call the introspective for device1, I get:

'<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"\n"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">\n<node><interface name="org.freedesktop.DBus.Introspectable"><method name="Introspect"><arg name="xml" type="s" direction="out"/>\n</method></interface><interface name="org.bluez.Device1"><method name="Disconnect"></method><method name="Connect"></method><method name="ConnectProfile"><arg name="UUID" type="s" direction="in"/>\n</method><method name="DisconnectProfile"><arg name="UUID" type="s" direction="in"/>\n</method><method name="Pair"></method><method name="CancelPairing"></method><property name="Address" type="s" access="read"></property><property name="Name" type="s" access="read"></property><property name="Alias" type="s" access="readwrite"></property><property name="Class" type="u" access="read"></property><property name="Appearance" type="q" access="read"></property><property name="Icon" type="s" access="read"></property><property name="Paired" type="b" access="read"></property><property name="Trusted" type="b" access="readwrite"></property><property name="Blocked" type="b" access="readwrite"></property><property name="LegacyPairing" type="b" access="read"></property><property name="RSSI" type="n" access="read"></property><property name="Connected" type="b" access="read"></property><property name="UUIDs" type="as" access="read"></property><property name="Modalias" type="s" access="read"></property><property name="Adapter" type="o" access="read"></property></interface><interface name="org.freedesktop.DBus.Properties"><method name="Get"><arg name="interface" type="s" direction="in"/>\n<arg name="name" type="s" direction="in"/>\n<arg name="value" type="v" direction="out"/>\n</method><method name="Set"><arg name="interface" type="s" direction="in"/>\n<arg name="name" type="s" direction="in"/>\n<arg name="value" type="v" direction="in"/>\n</method><method name="GetAll"><arg name="interface" type="s" direction="in"/>\n<arg name="properties" type="a{sv}" direction="out"/>\n</method><signal name="PropertiesChanged"><arg name="interface" type="s"/>\n<arg name="changed_properties" type="a{sv}"/>\n<arg name="invalidated_properties" type="as"/>\n</signal>\n</interface></node>'

Which doesn't have any details in for volume, transport control etc. Does that mean my bluez setup isn't permitting it?

Ultimately, I'd like to get this all working within Python so, Constantin, if you can continue your great support, some examples of python code would also be great.

Update 2

Ok, so trying this on a Rpi V3 with Bluez v5.23 and I get:

pi@raspberrypi:~ $ qdbus --system org.bluez /org/bluez/hci0/dev_A0_D7_95_A9_88_91
method QString org.freedesktop.DBus.Introspectable.Introspect()
property read QDBusObjectPath org.bluez.Device1.Adapter
property read QString org.bluez.Device1.Address
property readwrite QString org.bluez.Device1.Alias
property read ushort org.bluez.Device1.Appearance
property readwrite bool org.bluez.Device1.Blocked
property read uint org.bluez.Device1.Class
property read bool org.bluez.Device1.Connected
property read QString org.bluez.Device1.Icon
property read bool org.bluez.Device1.LegacyPairing
property read QString org.bluez.Device1.Modalias
property read QString org.bluez.Device1.Name
property read bool org.bluez.Device1.Paired
property read short org.bluez.Device1.RSSI
property readwrite bool org.bluez.Device1.Trusted
property read QStringList org.bluez.Device1.UUIDs
method void org.bluez.Device1.CancelPairing()
method void org.bluez.Device1.Connect()
method void org.bluez.Device1.ConnectProfile(QString UUID)
method void org.bluez.Device1.Disconnect()
method void org.bluez.Device1.DisconnectProfile(QString UUID)
method void org.bluez.Device1.Pair()
method QDBusVariant org.freedesktop.DBus.Properties.Get(QString interface, QString name)
method QVariantMap org.freedesktop.DBus.Properties.GetAll(QString interface)
signal void org.freedesktop.DBus.Properties.PropertiesChanged(QString interface, QVariantMap changed_properties
method void org.freedesktop.DBus.Properties.Set(QString interface, QString name, QDBusVariant value)
property read bool org.bluez.MediaControl1.Connected
method void org.bluez.MediaControl1.FastForward()
method void org.bluez.MediaControl1.Next()
method void org.bluez.MediaControl1.Pause()
method void org.bluez.MediaControl1.Play()
method void org.bluez.MediaControl1.Previous()
method void org.bluez.MediaControl1.Rewind()
method void org.bluez.MediaControl1.Stop()
method void org.bluez.MediaControl1.VolumeDown()
method void org.bluez.MediaControl1.VolumeUp()

... so MediaControl1 is shown.

Question is, is it due to the different version of Bluez or the different platform? It's also a bit slow to respond to the dbus commands (which I wasn't expecting). Sometimes it's almost immediate, other times is could take 1-2 seconds. Is that expected?

1

1 Answers

3
votes

First of all, you were in the right way when adding "Player1" to your object path, the documentation clearly states :

Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/playerX

However, how did you find that "Player1" ? This must not be a random number, you will find the exact string you have to append to the mac address by Introspecting your device (object path with dev_XX_XX...), usually in an xml tag ''


Now about your error, I know the documentation states that the Pause method does not take arguments, though maybe the documentation is not up-to-date ? One easy way to know a bit more about methods from the dbus is to analyze introspection : You can use the following command to retrieve the introspection (you should update the path after hci0 to match your object path) :

dbus-send --system --print-reply --dest=org.bluez /org/bluez/hci0 org.freedesktop.DBus.Introspectable.Introspect

To give you an example, the following is extracted from an introspection :

<method name="GetAll"><arg name="interface" type="s" direction="in"/><arg name="properties" type="a{sv}" direction="out"/>

From this I learn that the GetAll method takes as argument (direction in, type s) the interface name as a string, and returns a dictionary where strings(keys) are mapped to variants(values) (direction out, type a{sv})

Could you introspect your device and make sure you find that "Player1" node tag ? Then introspect that Player1 and make sure the Pause method does not take any argument.


To change the volume, I guess you're supposed to the use "Volume" property from the Interface org.bluez.MediaTransport1 (look at the end of the media-api.txt file).


Finally, do you plan to include these manipulations in a program ? If yes, I suggest using dbus bindings in the appropriate language (C, Glib, Python, C#...). I can give you more documentation on how to interact with bluez in a program.

UPDATE

The version on your rpi (5.23) is old and uses an API which is now deprecated (see for yourself the org.bluez.MediaControl1 interface has [Deprecated] everywhere here https://git.kernel.org/cgit/bluetooth/bluez.git/tree/doc/media-api.txt)

By looking at the doc, theres no interfaces you should be able to see when introspecting the device. org.bluez.Media1 should appear if you introspect /org/bluez/hci0 (refering to the specified Object path).

Service     org.bluez
Interface   org.bluez.Media1
Object path [variable prefix]/{hci0,hci1,...}

And org.bluez.MediaPlayer1 should appear when introspecting the player :

Service     org.bluez (Controller role)
Interface   org.bluez.MediaPlayer1
Object path [variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX/playerX

Though in your case, I see nothing related to that "playerX" in your introspection and that's the issue that need to be fixed first.

To stream sound over Bluetooth, you want to use the A2DP Protocol with your linux as "source" and your speaker as "sink". This functionality (audio streaming) might require PulseAudio. Try to add the latest version of pulseaudio to your system (you might want to compile from sources). See here for the details : https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Bluetooth/

Maybe your rpi uses bluez4 and pulseaudio configured to connect automatically. This would explain why you can see the interfaces on the rpi.

There is also the Arch Wiki that can help for troubleshooting: https://wiki.archlinux.org/index.php/Bluetooth_headset#Headset_via_Bluez5.2FPulseAudio


While I was looking for information I found this website : http://www.lightofdawn.org/wiki/wiki.cgi/BluezA2DP

It describes how to use the bluez api to make it work for A2DP. The tutorial was written for bluez4 and the author talks about bluez5 compatibility at the end.

I can't tell exactly what actions and in what order you'll need to do, although you will find plenty of documentation and tutorials when googling "bluez a2dp python" (like this one on reddit, read carefully his comments at the top of his code file) : https://www.reddit.com/r/Python/comments/1f1xkt/use_python_to_turn_your_bluetooth_laptop_into_a/)

My guess is that you'll need to scan (org.bluez.Adapter1) ,register endpoint (org.bluez.Media1), pair (and maybe connect ? using org.bluez.Device1)and acquire a file descriptor for writing (org.bluez.MediaTransport1)

Before doing anything else, you'll need to see thoses interfaces using d-feet ! Once you have it working with d-feet, you can refer to this gist for a simple python example that will perform a scan : https://gist.github.com/CynaCons/8eb02540f87af5594fac489a9dca32c1

I've also uploaded an advanced version on github gist.