Tango event subscription and QT threads

Dear Tango Community,

I want to create a GUI for a camera in Python. I use PyQt6. One method to update the camera frame / GUI is polling, for example with QTimer. This works fine. However, to save hardware resources, I want the GUI to update only if a new image was aquired. Tango offers the very convenient event subscription methods for this, which is a perfect solution. The GUI subscribes to data_ready_events on the attribute camera_frame and updates the displayed frame, for example:

self.camera_frame_event_id = device.subscribe_event('camera_frame', tango.EventType.DATA_READY_EVENT, self.update_frame)


However, when I do this, I get the following error upon event reception / execution of self.update_frame():

QObject::startTimer: Timers can only be used with threads started with QThread


I did some google research and this a general PyQt "issue", but I could not find a general solution. The approach in QT would be to create the thread with QThread, but the thread created with Tango event subscription is not directly accessible.

Although this is more a Qt related issue than a Tango related issue, did anyone came about this already and maybe has a solution? I know that this is somehow already solved in Taurus / Sardana. How can I replicate it directly in PyQt6?

Thanks,
Dominik
Hi Dominik,

In Taurus we translate Tango events into Qt signals as early as possible to avoid occupying the Tango Event Consumer Thread and to avoid the kind of problems you mention. There is just some basic decoding of the Tango event value into the Taurus event value (CS agnostic) done done on this thread (Tango). This decoding could be even deledated into a pure python worker thread or a pool of threads. This should be somehow demonstrated by this picture: https://gitlab.com/taurus-org/taurus/-/wikis/Taurus-webinar-(for-maintainers)#event-handling-chain-in-a-taurus-widget. Also revieving the TaurusBaseComponent code could be usefull: https://gitlab.com/taurus-org/taurus/-/blob/develop/lib/taurus/qt/qtgui/base/taurusbase.py#L112.

Hope this helps you.

Cheers,
Zibi
Hi Zibi,

This helped, thank you!

For anyone reading this post later, here is what I did:
1. Subscribe to the event but as the function, DO NOT use your generic method to update the frame as I tried earlier. (see my original post above). Use a custom function, which I simply called tmp() in this case.
self.camera_frame_event_id = device.subscribe_event('camera_frame', tango.EventType.DATA_READY_EVENT, self.tmp)

2. The function tmp() simply needs to emit a Qt signal that triggers the function update_frame(). I used the signal of another button that I had already implemented that executes the function update_frame() on button click. So you are essentially "simulating" a button click when a Tango event is recieved.
def tmp(self, input):
self.button_update_frame.clicked.emit()

3. The button "update_frame" needs to be connected to the update_frame function off course:
self.button_update_frame.clicked.connect(lambda: self.update_frame())


I hope this is usefull to future readers.

Cheers,
Dominik
Edited 1 year ago
 
Register or login to create to post a reply.