Search code examples
c++linuxqtdbus

How to create / instantiate pairing agent on D-Bus with Qt


I'm trying to build a Qt application for pairing / connecting bluetooth devices on a Linux device. I'm using the BlueZ API (https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc) over D-Bus as descibed in the BlueZ official site (http://www.bluez.org/page/8/).

The general procedure is described in here: https://www.kynetics.com/docs/2018/pairing_agents_bluez/

At the moment, I can use the APIs for pairing/connecting devices without problems.

BUT I cannot figure out how to use the pairing agent.

I'm trying to follow the direction pointed out here: How can i set the bluetooth pin number in linux C/C++

QDBusAbstractAdaptor object does not export implemented dbus interface

I searched a lot on the web and there are tons of code:

https://github.com/sdemario/qt-bluez-demos

https://github.com/KDE/bluez-qt

https://android.googlesource.com/platform/external/bluetooth/bluez/+/froyo-release/test/agent.c

https://github.com/pauloborges/bluez/blob/master/client/agent.c

https://www.linumiz.com/bluetooth-connectdevice-without-scanning/

but, still, I can't understand how this works. There are also a lot of scripts in python out there, but I'm working with C++.

My (pseudo)-code is the following:

agentManager = new QDBusInterface("org.bluez","/org/bluez","org.bluez.AgentManager1",QDBusConnection::systemBus(),this);
agent = new QDBusInterface("org.bluez","/org/bluez/agent","org.bluez.Agent1",QDBusConnection::systemBus(),this);


QDBusReply<void> reply;

reply = agentManager->call("RegisterAgent", QVariant::fromValue(QDBusObjectPath(path)), capability);
if (!reply.isValid()) {
    qDebug() << reply.error().message() << reply.error().name();
}

reply = agentManager->call("RequestDefaultAgent", QVariant::fromValue(QDBusObjectPath(path)));
if (!reply.isValid()) {
    qDebug() << reply.error().message() << reply.error().name();
}

QDBusReply<QString> reply_str = agent->call("RequestPinCode", QVariant::fromValue(QDBusObjectPath(path)));
if (reply_str.isValid()) {
    pincode = reply_str.value();
} else {
    qDebug() << reply_str.error().message() << reply.error().name();
}

The "RegisterAgent" and "RequestDefaultAgent" calls give a valid reply, while, the "RequestPinCode" gives an error:

"Method "RequestPinCode" with signature "o" on interface "org.bluez.Agent1" doesn't exist " "org.freedesktop.DBus.Error.UnknownObject"

Why? What I'm doing wrong?

Thanks

EDIT

I implemented a brand new service on D-Bus, which implements the org.bluez.Agent1 interface (at leat a part of it). Now, the code looks like this:

MyObject* myObject = new MyObject();
MyAdaptor* myAdaptor = new MyQDusAdaptor(myObject);

if (QDBusConnection::systemBus().registerService("org.test")) {
    qDebug() << "registerService was Succesfull!";
} else {
    qDebug() << "registerService was not Succesfull!";
}

if(QDBusConnection::systemBus().registerObject("/pairing/agent",myObject)){
    qDebug() << "registerObject was Succesfull!";
} else {
    qDebug() << "registerObject was not Succesfull!";
}

agentManager->RegisterAgent(QDBusObjectPath("/pairing/agent"),"NoInputNoOutput");
agentManager->RequestDefaultAgent(QDBusObjectPath("/pairing/agent"));


where the MyObject is


class MyObject : public QObject
{
    Q_OBJECT

public:
    MyObject();

public Q_SLOTS:
    QString RequestPinCode(const QDBusObjectPath &in0);
};


MyObject::MyObject() {

}
QString MyObject::RequestPinCode(const QDBusObjectPath& in0)
{
    qDebug() << "MyObject" << in0.path();


    QString str = "2974";
    return str;
}


and the adaptor is


class MyQDusAdaptor: public QDBusAbstractAdaptor
{

    Q_OBJECT
    Q_CLASSINFO("D-Bus Interface", "org.bluez.Agent1")
    Q_CLASSINFO("D-Bus Introspection", ""
                                                                         "  <interface name=\"org.bluez.Agent1\">\n"
                                                                         "    <method name=\"RequestPinCode\">\n"
                                                                         "      <arg direction=\"in\" type=\"o\"/>\n"
                                                                         "      <arg direction=\"out\" type=\"s\"/>\n"
                                                                         "    </method>\n"
                                                                         "  </interface>\n"
                                                                         "")

public:
        MyQDusAdaptor(QObject *parent);
        virtual ~MyQDusAdaptor();

public: // PROPERTIES
public Q_SLOTS: // METHODS
        QString RequestPinCode(const QDBusObjectPath &in0);
};


MyQDusAdaptor::MyQDusAdaptor(QObject* parent) : QDBusAbstractAdaptor(parent) {

    setAutoRelaySignals(true);
}

MyQDusAdaptor::~MyQDusAdaptor() {

}

QString MyQDusAdaptor::RequestPinCode(const QDBusObjectPath& in0)
{
    qDebug() << "MyQDusAdaptor" << in0.path();

    QString out0;
         QMetaObject::invokeMethod(parent(), "RequestPinCode", Q_RETURN_ARG(QString, out0), Q_ARG(QDBusObjectPath, in0));
         return out0;
}

When I try to pair a smartphone, the application freezes for ca 30s, I recieve the following error message:

"Did not receive a reply. Possible causes include: the remote application did not send a reply, the message bus security policy blocked the reply, the reply timeout expired, or the network connection was broken." "org.freedesktop.DBus.Error.NoReply"

and only after that the smartphone ask to insert a PIN.

I think that has to be done asynchronously. Any tips?


Solution

  • Ok, now it works. The way to do it is explained in QDBusAbstractAdaptor object does not export implemented dbus interface

    Details of the implementation are in the last EDIT of this question. The last thing to to is to implement the "Pair" method on the Device1 interface asynchronously with callWithCallback:

    void cBluetoothDevice::Pair() {
    
        QList<QVariant> args;
        args.clear();
    
        device->callWithCallback("Pair",args, this, SLOT(PairSlot(QDBusMessage)));
    }
    
    

    where device is a QDBusInterface*