RSS Atom Add a new post titled:
Browse Avahi services through DBUS using PyQt5 and QtDbus

I needed to do service autodiscovery for a digital signage application frontend. The frontend consists of Python3 code and some QML that displays some media elements and an embedded webkit element.

This application needs to find a server that will provide it with provisioning information and later on with the content to display. In order to automate the discovery of possible servers I decided to use Avahi over DBUS.

The ide is to start the application which will then scan for suitable services in the local broadcast domain. Each discovered service is resolved to get its hostname and port and is appended to a ListView. From there the deployer can select which server to use for this display.

Now that the application itself is written using PyQt5, using the QtDbus package was the most sane thing to do. At least I thought so. It turned out that QtDbus is not that simple when used in PyQt5.

So in order to spare others from plucking their hair out over how to wire those components together when browsing for services, I'm publishing my implementation. It is tested with PyQt 5.6 and PYthon 3.5. It still has some rough edges, like to handling the Failure and CacheExhausted signals. I also mirroed it on Github Gists.

#!/usr/bin/python3

import sys
import logging
import signal

from PyQt5.QtCore import QObject, QVariant, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QApplication
from PyQt5.QtDBus import QDBus, QDBusConnection, QDBusInterface, QDBusMessage

from models import Server

logger = logging.getLogger(__name__)
signal.signal(signal.SIGINT, signal.SIG_DFL)


class Service(QObject):

    def __init__(
        self,
        interface,
        protocol,
        name,
        stype,
        domain,
        host=None,
        aprotocol=None,
        address=None,
        port=None,
        txt=None
    ):
        super(Service, self).__init__()
        self.interface = interface
        self.protocol = protocol
        self.name = name
        self.stype = stype
        self.domain = domain
        self.host = host
        self.aprotocol = aprotocol
        self.address = address
        self.port = port
        self.txt = txt


    def __str__(self):
        return '{s.name} ({s.stype}.{s.domain})'.format(s=self)

    def __eq__(self, other):
        return self.__dict__ == other.__dict__


class Discoverer(QObject):

    # Use those signals to get notified of changes in subscribed services
    # Emitted when initial scanning of avahi services is done
    initialized = pyqtSignal()
    # Emitted when a new service for our watched type is found
    added = pyqtSignal(Service)
    removed = pyqtSignal(Service)

    def __init__(self, parent, service, interface=-1, protocol=-1, domain='local'):
        super(Discoverer, self).__init__(parent)
        self.protocol = protocol
        self.bus = QDBusConnection.systemBus()
        self.bus.registerObject('/', self)
        self.server = QDBusInterface(
            'org.freedesktop.Avahi',
            '/',
            'org.freedesktop.Avahi.Server',
            self.bus
        )
        flags = QVariant(0)
        flags.convert(QVariant.UInt)
        browser_path = self.server.call(
            'ServiceBrowserNew',
            interface,
            self.protocol,
            service,
            domain,
            flags
        )
        logger.debug('New ServiceBrowser: {}'.format(browser_path.arguments()))
        self.bus.connect(
            'org.freedesktop.Avahi',
            browser_path.arguments()[0],
            'org.freedesktop.Avahi.ServiceBrowser',
            'ItemNew',
            self.onItemNew
        )
        self.bus.connect(
            'org.freedesktop.Avahi',
            browser_path.arguments()[0],
            'org.freedesktop.Avahi.ServiceBrowser',
            'ItemRemove',
            self.onItemRemove
        )
        self.bus.connect(
            'org.freedesktop.Avahi',
            browser_path.arguments()[0],
            'org.freedesktop.Avahi.ServiceBrowser',
            'AllForNow',
            self.onAllForNow
        )

    @pyqtSlot(QDBusMessage)
    def onItemNew(self, msg):
        logger.debug('Avahi service discovered: {}'.format(msg.arguments()))
        flags = QVariant(0)
        flags.convert(QVariant.UInt)
        resolved = self.server.callWithArgumentList(
            QDBus.AutoDetect,
            'ResolveService',
            [
                *msg.arguments()[:5],
                self.protocol,
                flags
            ]
        ).arguments()
        logger.debug('Avahi service resolved: {}'.format(resolved))
        service = Service(*resolved[:10])
        self.added.emit(service)

    @pyqtSlot(QDBusMessage)
    def onItemRemove(self, msg):
        arguments = msg.arguments()
        logger.debug('Avahi service removed: {}'.format(arguments))
        service = Service(*arguments[:5])
        self.removed.emit(service)

    @pyqtSlot(QDBusMessage)
    def onAllForNow(self, msg):
        logger.debug('Avahi emitted all signals for discovered peers')
        self.initialized.emit()


# Main Function
if __name__ == '__main__':

    logging.basicConfig(level=logging.DEBUG)

    # Create main app
    app = QApplication(sys.argv)

    # Create avahi discoverer
    d = Discoverer(app, '_workstation._tcp')

    # Execute the application and exit
    sys.exit(app.exec_())#!/usr/bin/python3
Posted
Herbstwanderung Reichenstein 2014

Am letzten Septembertag 2014 ging es um 05:30 von Graz aus auf den Präbichl. Die Sonne war noch nicht ganz über die Berge im Osten aufgegangen, als der Aufsteig begann. Vom Parkplatz beim Präbichlerhof ging es über den Theklasteig rauf auf den Reichensteingipel. Kurz vor 09:00, nach nicht ganz zwei Stunden Aufsteig war ich oben angelengt. Die Aussicht war toll, überall in den Tälern lag noch der Nebel und ich konnte eine unglaubliche Fernsicht geniesen.

Posted
Unser Hochzeits-Video

Marija Kanizaj hat für uns nicht nur die Fotos auf unserer Hochzeit gemacht, sie hat uns auch mit einem Video-Fotobuch überracht, das nochmal die schönsten Momente dieses Tages zusammenfasst.

Die hochauflösende Version mit 650MB gibt es auch als Download.

Posted
Die Hochzeitsgäste bei der Fotobox

Während wir unsere Hochzeitsfotos machen liessen, hatten unsere Gäste die Gelegeneheit, und ein paar Fotos von ihnen als andenken zu schiessen. Der Spass stand dabei natürlich im Vordergrund, weshalb es auch viele Accessoires und Verkleidungen gab. Die dabei entstandenen Aufnahmen möchten wir euch natürlich nicht vorenthalten.

Posted
Hochzeits-Feuerwerk

Danke an unsere Pyrotechniker, die das tolle Feuerwerk am Abend unserer Hochzeit arrangiert und gefilmt haben!

Die hochauflösende Version mit 700MiB gibt es auch als Download.

Posted
Peter´s Fotos von der Hochzeit

Peter, der ja auch mein Beistand auf der Hochzeit war, hat uns seine Fotos übermittelt, hier eine kleine Auswahl und den ersten Fotos aus dem Rittersaal auf Schloss Kornberg.

Posted
Erste Eindrücke von unserer Hochzeit

Wir haben es geschafft! Am 30.05.2014 haben Christina und ich uns auf Schloss Kornberg bei Feldbach vor versammelter Familie und Freunden das Ja-Wort gegeben. Vielen lieben Dank an alle, die dies mit uns gefeiert haben und an alle, die für diesen wundervollen Tag gesorgt haben.

Hier einige erste Eindrücke von unserer Fotografin Marja Kanižaj ...

Posted
Shared screen sessions over SSH

When you need to deploy software or simply work together with someone from far away you usually have several choices on how to accomplish it. The simple solution is to use something like TeamViewer but if all you need to share is a shell, there an easier way.

using screen it is possible to share a shell session across several SSH sessions. The first thing to do is install screen:

aptitude install screen

Now you have to decide which user should be the host for the shared sessions, i.e. which privileges are required during the session. I would recommend to use an unprivileged user.

Next, ask all your external partners to generate a SSH key pair and send your their public key (preferably in an encrypted email).

For each user who will participate in the shared session, create a separate user

Posted
Sonnenaufgangswanderung 2013

Am 1. November haben wir es mal wieder geschafft, Christine kam am Vortag bereits von der Ramsau nach Kindberg und wir brachen um 05:30 von Kindtal aus auf um beim Sonnenaufgang um 6:40 bereits bei den drei Wetterkreuzen zu sein.

Durch den Nebel, der sich über Nacht im Tal gesammelt hatte, gingen wir vom Sagbauern weg bis wir kurz unterhalb der Wetterkreuze endlich freien Nachthimmel über uns hatten und die Sonne sich schon als heller Schimmer am Horizont ankündigte.

Wir wärmten uns bei den Wetterkreuzen mit Tee auf und mit ca. 5 Minuten Verspätung, der Nebel überzog im Osten noch die Fischbacher Alpen, erschien dann die Sonne langsam über dem Nebel.

Bei doch schon sehr frostigen Temperaturen liessen wir uns kurz in den ersten Sonnenstrahlen am Waldrand nieder und frühstückten, bevor wir dann wieder in den Nebel, 100 Meter unter uns, abstiegen und nach Kindtal zurückkehrten.

Posted

This blog is powered by ikiwiki.