QThread, Slots, Signals, meta type information error

classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

QThread, Slots, Signals, meta type information error

Repscher, Benedikt
I'm having an Issue regarding QThreads, Slots and Signals and meta type information.

The Issue arises when I try to connect a signal, e.g.
QBluetoothDeviceDiscoveryAgent.deviceDiscovered
to a slot, e.g.
deviceDiscovered(device)
where the device is of type QBluetoothDeviceInfo

The signal-slot connection is done from within a QThread.

The error that arises is
QObject::connect: Cannot queue arguments of type 'QBluetoothDeviceInfo'
(Make sure 'QBluetoothDeviceInfo' is registered using qRegisterMetaType().)

I've tried generating the meta type info explicitly using
char_id = QMetaType.type("QBluetoothDeviceInfo")
this doesn't help though (and returns 0 anyways)


What really baffles me is, that this error only occurs when I implement the QThread the traditional way by subclassing QThread and overriding run().
When I use the approach with the controller that moves the QObject to a QThread (using moveToThread()), then the code works.

------------------------ code that works ------------------------
from PyQt5.QtBluetooth import QLowEnergyController,\
    QBluetoothDeviceDiscoveryAgent, QBluetoothDeviceInfo
from PyQt5.QtCore import pyqtSlot, QThread, QCoreApplication, QObject,\
    pyqtSignal
import sys

class BTLEWorker(QObject):    
    def __init__(self, app:QCoreApplication=None, parent:QObject=None, *args, **kwargs):    
        self.app = app
           
        self.discovery_agent = QBluetoothDeviceDiscoveryAgent
        self.le_controller = QLowEnergyController
                       
        super(BTLEWorker, self).__init__(parent=parent, *args, **kwargs)
                   
    def run(self):
        self.discovery_agent = QBluetoothDeviceDiscoveryAgent()
                               
        self.discovery_agent.deviceDiscovered.connect(self.onDeviceDiscovered)
        self.discovery_agent.finished.connect(self.onDeviceDiscoveryFinished)
        self.discovery_agent.error.connect(self.onDeviceDiscoveryError)
        self.discovery_agent.setLowEnergyDiscoveryTimeout(1000)
        self.discovery_agent.start()
   
        self.enterLoop()
                           
    def enterLoop(self):
        th = self.thread()
        th.exec_()
       
    def exitLoop(self):
        th = self.thread()
        th.exit()
     
    #      
    # Slots for QDeviceDiscoveryAgent
    #                
    @pyqtSlot(QBluetoothDeviceInfo)
    def onDeviceDiscovered(self, devinfo: QBluetoothDeviceInfo):
        address = devinfo.address() #: :type address: QBluetoothAddress
       
        if devinfo.coreConfigurations() & QBluetoothDeviceInfo.LowEnergyCoreConfiguration:
            print("Last device discovered", devinfo.name(), address.toString())
           
    @pyqtSlot()
    def onDeviceDiscoveryFinished(self):
        print("Discovery finished")
       
        self.exitLoop()
   
    @pyqtSlot(QBluetoothDeviceDiscoveryAgent.Error)
    def onDeviceDiscoveryError(self, error: QBluetoothDeviceDiscoveryAgent.Error):
        print("Bluetooth error", error)
       
        self.exitLoop()
   

class Controller(QObject):
    operate = pyqtSignal()
   
    def __init__(self, app=None, parent=None, *args, **kwargs):
        """
        @type app: QApplication
        @type parent: QObject
        """
        super(Controller, self).__init__(parent=parent, *args, **kwargs)
               
        self.app = app
       
        self.worker = BTLEWorker()
        self.worker_thread = QThread()
        self.worker.moveToThread(self.worker_thread)
       
        self.worker_thread.finished.connect(self.quit)
        self.operate.connect(self.worker.run)
               
        self.worker_thread.start()
               
    @pyqtSlot()
    def quit(self):
        """
        @rtype int
        """
        self.app.exit()
       
               
if __name__ == '__main__':    
    qapp = QCoreApplication(sys.argv)
   
    c = Controller(qapp)
    c.operate.emit()
   
    sys.exit(qapp.exec_())
------------------------ end of code that works ------------------------

------------------------ code that DOENST work ------------------------
from PyQt5.QtBluetooth import QLowEnergyController,\
    QBluetoothDeviceDiscoveryAgent, QBluetoothDeviceInfo
from PyQt5.QtCore import pyqtSlot, QThread, QCoreApplication, QObject
import sys


class BTLEWorker(QThread):    
    def __init__(self, app:QCoreApplication=None, parent:QObject=None, *args, **kwargs):    
        self.app = app
           
        self.discovered_devices = list()
       
        self.discovery_agent = QBluetoothDeviceDiscoveryAgent
        self.le_controller = QLowEnergyController
                       
        super(BTLEWorker, self).__init__(parent=parent, *args, **kwargs)
               
    def run(self):
        self.discovery_agent = QBluetoothDeviceDiscoveryAgent()
                               
        self.discovery_agent.deviceDiscovered.connect(self.onDeviceDiscovered) #THIS FAILES
        self.discovery_agent.finished.connect(self.onDeviceDiscoveryFinished)
        self.discovery_agent.error.connect(self.onDeviceDiscoveryError)
        self.discovery_agent.setLowEnergyDiscoveryTimeout(1000)
        self.discovery_agent.start()
   
        self.enterLoop()
       
        self.app.exit()
                           
    def enterLoop(self):
        self.exec_()
       
    def exitLoop(self):
        self.exit()
     
    #      
    # Slots for QDeviceDiscoveryAgent
    #                
    @pyqtSlot(QBluetoothDeviceInfo)
    def onDeviceDiscovered(self, devinfo: QBluetoothDeviceInfo):
        address = devinfo.address() #: :type address: QBluetoothAddress
       
        if devinfo.coreConfigurations() & QBluetoothDeviceInfo.LowEnergyCoreConfiguration:
            self.discovered_devices.append(devinfo)
            print("Last device discovered", devinfo.name(), address.toString())
           
    @pyqtSlot()
    def onDeviceDiscoveryFinished(self):
        print("Discovery finished")
       
        self.exitLoop()
   
    @pyqtSlot(QBluetoothDeviceDiscoveryAgent.Error)
    def onDeviceDiscoveryError(self, error: QBluetoothDeviceDiscoveryAgent.Error):
        print("Bluetooth error", error)
       
        self.exitLoop()    
   
               
if __name__ == '__main__':    
    qapp = QCoreApplication(sys.argv)
   
    c = BTLEWorker(qapp)
    c.start()
   
    sys.exit(qapp.exec_())        
------------------------ end of code that DOENST work ------------------------

I would really like to understand whats happening here.
I couldn't find any answer when looking through the Qt and PyQt documentations

Best regards

Benedikt
   

_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt
Reply | Threaded
Open this post in threaded view
|

Re: QThread, Slots, Signals, meta type information error

Giuseppe Corbelli
On 3/1/19 5:39 PM, Repscher, Benedikt wrote:

> I'm having an Issue regarding QThreads, Slots and Signals and meta type information.
>
> The Issue arises when I try to connect a signal, e.g.
> QBluetoothDeviceDiscoveryAgent.deviceDiscovered
> to a slot, e.g.
> deviceDiscovered(device)
> where the device is of type QBluetoothDeviceInfo
>
> The signal-slot connection is done from within a QThread.
>
> The error that arises is
> QObject::connect: Cannot queue arguments of type 'QBluetoothDeviceInfo'
> (Make sure 'QBluetoothDeviceInfo' is registered using qRegisterMetaType().)

Well, I don't know the internals but the call should be generated by
SIP. The QBluetoothDeviceInfo class definition in SIP looks fine at a
first glance.

> I've tried generating the meta type info explicitly using
> char_id = QMetaType.type("QBluetoothDeviceInfo")
> this doesn't help though (and returns 0 anyways)
>
>
> What really baffles me is, that this error only occurs when I
> implement the QThread the traditional way by subclassing QThread and
> overriding run().
> When I use the approach with the controller that moves the QObject
> to  a QThread (using moveToThread()), then the code works.
I don't see anything wrong with your code, BUT:
https://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/

--
Giuseppe Corbelli
_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt
Reply | Threaded
Open this post in threaded view
|

Re: QThread, Slots, Signals, meta type information error

Repscher, Benedikt
Thank you for your reply.
I read the article "you're doing it wrong" and learned some things from it. However, it didn't help me resolve my issue.


I sort of resvoled the issue.
I discovered the difference because the code that I actually use looks a little bit different (uses 2 QThreads). However, this code raised the same error, even though it uses the moveTothread()-approach.


Anyways...
The code that I posted here that uses the moveToThread()-approach works, because the signal belongs to the same object as the slot. Therefore, Qt seems to use a Qt.DirectConnection (non-queued).
However, when you want to use QueuedConnection (which is the default when usign the QThread-subclassing-approach) then the connection fails because of lacking meta-info.

Therefore, IMHO, the problem with missing meta-info still persists. The moveToThread()-approach just works around the issue, because it doesn't use the queued connection.

Consider the matter closed.

-----Original Message-----
From: Giuseppe Corbelli <[hidden email]>
Sent: Monday, March 4, 2019 9:20 AM
To: Repscher, Benedikt <[hidden email]>; [hidden email]
Subject: Re: [PyQt] QThread, Slots, Signals, meta type information error

On 3/1/19 5:39 PM, Repscher, Benedikt wrote:

> I'm having an Issue regarding QThreads, Slots and Signals and meta type information.
>
> The Issue arises when I try to connect a signal, e.g.
> QBluetoothDeviceDiscoveryAgent.deviceDiscovered
> to a slot, e.g.
> deviceDiscovered(device)
> where the device is of type QBluetoothDeviceInfo
>
> The signal-slot connection is done from within a QThread.
>
> The error that arises is
> QObject::connect: Cannot queue arguments of type 'QBluetoothDeviceInfo'
> (Make sure 'QBluetoothDeviceInfo' is registered using
> qRegisterMetaType().)

Well, I don't know the internals but the call should be generated by SIP. The QBluetoothDeviceInfo class definition in SIP looks fine at a first glance.

> I've tried generating the meta type info explicitly using char_id =
> QMetaType.type("QBluetoothDeviceInfo")
> this doesn't help though (and returns 0 anyways)
>
>
> What really baffles me is, that this error only occurs when I
> implement the QThread the traditional way by subclassing QThread and
> overriding run().
> When I use the approach with the controller that moves the QObject to  
> a QThread (using moveToThread()), then the code works.
I don't see anything wrong with your code, BUT:
https://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/

--
Giuseppe Corbelli
_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt
Reply | Threaded
Open this post in threaded view
|

Re: QThread, Slots, Signals, meta type information error

Giuseppe Corbelli
On 3/4/19 5:09 PM, Repscher, Benedikt wrote:

> Thank you for your reply.
> I read the article "you're doing it wrong" and learned some things
> from it. However, it didn't help me resolve my issue.
>
> I sort of resvoled the issue.
> I discovered the difference because the code that I actually use
> looks  a little bit different (uses 2 QThreads). However, this code raised the
> same error, even though it uses the moveTothread()-approach.
>
>
> Anyways...
> The code that I posted here that uses the moveToThread()-approach
> works, because the signal belongs to the same object as the slot.
> Therefore, Qt seems to use a Qt.DirectConnection (non-queued).
> However, when you want to use QueuedConnection (which is the default
> when usign the QThread-subclassing-approach) then the connection fails
> because of lacking meta-info.
>
> Therefore, IMHO, the problem with missing meta-info still persists.
> The moveToThread()-approach just works around the issue, because it
> doesn't use the queued connection.
>
> Consider the matter closed.

Well, not really :-)

I don't have a pyqt+btle installation but, strictly Qt speaking:
"A single QMetaObject instance is created for each QObject subclass that
is used in an application [...]"
And QBluetoothDeviceInfo is not a QObject subclass.

AFAIK (little and less, admittedly) however your issue is related to the
serialization that must be done to copy the object so that the target
thread can have its own instance. In turn this means that QMetaType info
should be available for the class, and in fact it is by looking at the
Q_DECLARE_METATYPE(QBluetoothDeviceInfo) line in qbluetoothdeviceinfo.h

So basically I cannot be of much help, since everything seems to be in
the right place.

Did you try the connection without the pyqtSlot decorator?

Out of curiosity: why are you explicitly calling the underlying
thread().exec_() in a QObject moved to that thread?

--
Giuseppe Corbelli
_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt
Reply | Threaded
Open this post in threaded view
|

Re: QThread, Slots, Signals, meta type information error

Repscher, Benedikt
> I don't have a pyqt+btle installation but, strictly Qt speaking:
> "A single QMetaObject instance is created for each QObject subclass that is used in an application [...]"
I too read that somewhere
> And QBluetoothDeviceInfo is not a QObject subclass.
This though, I hadn't noticed yet.

> AFAIK (little and less, admittedly) however your issue is related to the serialization that must be done to copy the object so that the target thread can have its own instance. In turn this means that QMetaType info should be available for the class, and in fact it is by looking at the
> Q_DECLARE_METATYPE(QBluetoothDeviceInfo) line in qbluetoothdeviceinfo.h
OK, but I'm guessing declaring and registering are two different things, otherwise this error:
QObject::connect: Cannot queue arguments of type 'QBluetoothDeviceInfo'
(Make sure 'QBluetoothDeviceInfo' is registered using qRegisterMetaType().)
Wouldn’t be raised.

> So basically I cannot be of much help, since everything seems to be in the right place.
> Did you try the connection without the pyqtSlot decorator?
Yes, it doesn't change a thing.

> Out of curiosity: why are you explicitly calling the underlying
> thread().exec_() in a QObject moved to that thread?
So the signals that are received by the slots are processed.
I'm guessing this question is a hint towards my suboptimal usage of the moveToThread()-approach.
I wanted to have as minimal differences between both of the implementations as possible...
_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt
Reply | Threaded
Open this post in threaded view
|

Re: QThread, Slots, Signals, meta type information error

Giuseppe Corbelli
On 3/5/19 1:56 PM, Repscher, Benedikt wrote:

>> I don't have a pyqt+btle installation but, strictly Qt speaking:
>> "A single QMetaObject instance is created for each QObject subclass that is used in an application [...]"
> I too read that somewhere
>> And QBluetoothDeviceInfo is not a QObject subclass.
> This though, I hadn't noticed yet.
>
>> AFAIK (little and less, admittedly) however your issue is related to the serialization that must be done to copy the object so that the target thread can have its own instance. In turn this means that QMetaType info should be available for the class, and in fact it is by looking at the
>> Q_DECLARE_METATYPE(QBluetoothDeviceInfo) line in qbluetoothdeviceinfo.h
> OK, but I'm guessing declaring and registering are two different things, otherwise this error:
> QObject::connect: Cannot queue arguments of type 'QBluetoothDeviceInfo'
> (Make sure 'QBluetoothDeviceInfo' is registered using qRegisterMetaType().)
> Wouldn’t be raised.

Well, seems to me this is a QT bug. To use queued connections you also
need the qRegisterMetaType<QBluetoothDeviceInfo>() call.
Looking at qtconnectivity sources 1b19d7 the call is not made in bluez
implementation while it is on other platforms (android, osx, ios,
winrt). That seems the root cause.
If you have your Qt environment I would try to add the missing call to
bluez platform and if it solves the issue file a bug against current Qt.

--
Giuseppe Corbelli
_______________________________________________
PyQt mailing list    [hidden email]
https://www.riverbankcomputing.com/mailman/listinfo/pyqt