přihlásit 4477/805789

D-BUS v Pythonu

V Pythonu je, jak jsme zvyklí, D-BUS velmi kvalitně implementován podle pythoních standardů a zásad. Vše je namapováno na Pythoní objekty a používání D-BUS v Pythonu je přirozené a snadné. D-BUS je v Pythonu dostupný přes modul dbus.

Důležité upozornění, dbus je stále ve vývoji a nemá zatím stabilní API. Na internetu se proto můžete setkat s ukázkami, které nefungují. Ve skutečnosti mi dalo práci přijít na to, jak to teď funguje a musel jsem louskat zdrojové kódy HAL monitoru, abych na to přišel. Zde uvedené ukázky jsou platné pro dbus verze 0.40.0 a jsou odzkoušeny na Auroxu 11 (FC4).

Základní použití

Například zde je ukázka, jak využít službu DBus a získat seznam názvů všech registrovaných služeb. Příklad má dvě varianty. Obě mají společné to, že se nejprve vytvoří připojení k BUS daemonu (buď k systémovému nebo uživatelskému). Následně si vyžádáme proxy objekt služby:

 1 import dbus
 2 
 3 SLUZBA   = "org.freedesktop.DBus"
 4 OBJEKT   = "/org/freedesktop/DBus"
 5 ROZHRANI = "org.freedesktop.DBus"
 6 
 7 bus   = dbus.SessionBus()
 8 proxy = bus.get_object(SLUZBA, OBJEKT)
 9 
10 for name in proxy.ListNames(dbus_interface = ROZHRANI):
11     print name

ukázka č. 1

 1 import dbus
 2 
 3 SLUZBA   = "org.freedesktop.DBus"
 4 OBJEKT   = "/org/freedesktop/DBus"
 5 ROZHRANI = "org.freedesktop.DBus"
 6 
 7 bus    = dbus.SystemBus()
 8 proxy  = sessBus.get_object(SLUZBA, OBJEKT)
 9 slDBus = dbus.Interface(proxy, ROZHRANI)
10 
11 for name in slDBus.ListNames():
12     print name

ukázka č. 2

Varianty se liší v použití proxy objektu a rozhraní. První případ se hodí, když pracujeme se spoustou objektů, ale jen velmi krátce, třeba jen jednou, pak je zbytečné připojovat rozhraní k objektu. Druhý způsob se hodí, když s objektem chceme pracovat dlouhodobě.

Asynchronní volání

Obě varianty mají jednu společnou vlastnost, jsou synchronní. D-BUS umožňuje i asynchronní komunikaci. Synchronní komunikace znamená, že náš program o něco požádá službu a pak čeká, dokud služba neodpoví. U asynchronní komunikace to funguje tak, že program o něco požádá službu a předá ji handler (funkci), který má služba použít, až bude mít výsledek. Mezi tím si náš program může dělat co chce. To je trochu náročnější, ale obvykle lepší řešení, zvláště u GUI aplikací, kde je i drobné prodlení hned viditelné na chvilkovém zamrzání GUI. Takže to samé co jste viděli výše, ale asynchronně:

 1 import gobject
 2 import dbus
 3 
 4 SLUZBA   = "org.freedesktop.DBus"
 5 OBJEKT   = "/org/freedesktop/DBus"
 6 ROZHRANI = "org.freedesktop.DBus"
 7 
 8 def hPrintNames(list):
 9     for name in list:
10         print name
11 
12 def hPrintError(msg):
13     print msg
14 
15 bus    = dbus.SessionBus()
16 proxy  = bus.get_object(SLUZBA, OBJEKT)
17 slDBus = dbus.Interface(proxy, ROZHRANI)
18 
19 slDBus.ListNames(reply_handler=hPrintNames,
20                  error_handler=hPrintError)
21 
22 print "hned pokračuji dál"
23 gobject.MainLoop().run()

ukázka č. 3

Na řádku 8 a 12 si vytvářím dva handlery. První je pro zachycení odpovědi, druhý pro zachycení případné chyby. Na řádku 19 volám službu, chci po ní seznam názvů registrovaných služeb a předávám jí oba handlery.

Program nebude čekat na odezvu, ale bude pokračovat dál, v našem případě se vypíše kontrolní zpráva a spustí mainloop, tedy čekací smyčku na události. Až služba bude mít připraven seznam, zavolá handler hPrintNames() a jako parametr mu předá onen seznam názvů. Handler pak tyto názvy vypíše v cyklu na obrazovku.

Ona čekací smyčka je z modulu gobject, který je součástí GTK. To znamená, že aby to fungovalo, tak D-BUS také musí využívat GTK a je na něm závislý. To je nežádoucí, protože někteří programátoři mohou preferovat jiný GUI toolkit a také je hloupost používat GUI toolkit u ne-GUI aplikace. Není to vlastnost D-BUS jako takového, ale jeho bindingu v Pythonu. Bylo to použito jako dočasné řešení při implementaci a od dbus modulu 0.41.0 je teoreticky možné (nutné) si zvolit implementaci čekací smyčky. Proto by ukázka měla správně začínat takto.

 1 import gobject
 2 import dbus
 3 
 4 if  getattr(dbus, "version", (0,0,0)) >= (0,41,0):
 5     import dbus.glib

ukázka č. 4

Novinka je na řádku 4 a 5, zbytek je stejný s předchozí ukázkou. Znalci GTK vědí, že glib je také modul GTK a z toho si odvodí, že importováním dbus.glib explicitně zapínáme v dbusu podporu pro GTK. Nejsem si jist, ale domnívám se, že podpora pro něco jiného není zatím implementována, ale je to řešení pro budoucnost.

Signály

Od asynchronní komunikace není daleko k signálům, které jsou svou podstatou nutně asynchronní. Signály si předvedeme u jiné služby, HAL (Hardware Abstract Layer - abstraktní vrstva hardware), kde pomocí signálů budeme zachycovat události přidání a odebrání zařízení. Při zachycení události vypíšeme ID zařízení, které bylo přidáno nebo odstraněno.

 1 import gobject
 2 import dbus
 3 
 4 if  getattr(dbus, "version", (0,0,0)) >= (0,41,0):
 5     import dbus.glib
 6 
 7 SLUZBA   = "org.freedesktop.Hal"
 8 OBJEKT   = "/org/freedesktop/Hal/Manager"
 9 ROZHRANI = "org.freedesktop.Hal.Manager"
10 
11 def hNoveZarizeni(halId):
12     print "Nové zařízení:", halId
13 
14 def hOdebranoZarizeni(halId):
15     print "Odebráno zařízení:", halId
16 
17 bus   = dbus.SystemBus()
18 proxy = bus.get_object(SLUZBA, OBJEKT)
19 slHal = dbus.Interface(proxy, ROZHRANI)
20 
21 slHal.connect_to_signal("DeviceAdded",   hNoveZarizeni)
22 slHal.connect_to_signal("DeviceRemoved", hOdebranoZarizeni)
23 
24 gobject.MainLoop().run()

ukázka č. 5

Jak je vidět, je to prakticky pořád to stejné. Pouze k předání handleru se používá metoda connect_to_signal(), která jako parametr přebírá název signálu a odkaz na handler.

Nevýhoda tohoto řešení je, že služba poskytující tento signál už musí existovat. To může dělat problémy především u spolupracujících služeb, u kterých by se musely hlídat jejich závislosti a pořadí jejich spouštění. Proto D-BUS umožňuje i zaregistrování žádosti o signál služby, která zatím neexistuje, ale v budoucnu existovat může. V takovém případě se nevytváří proxy objekt služby, protože v době volání ještě nemusí existovat.

17 bus  = dbus.SystemBus()
18 
19 bus.add_signal_receiver(hNoveZarizeni,    'DeviceAdded',
20                         ROZHRANI, SLUZBA, OBJEKT)
21 bus.add_signal_receiver(hOdebranoZarizeni,'DeviceRemoved',
22                         ROZHRANI, SLUZBA, OBJEKT)
23 
24 gobject.MainLoop().run()

ukázka č. 6

Od D-BUS verze 0.36 a dbus verze (0, 43, 0) je možno ještě přidat další argumenty, které se při vzniku signálu předají handleru. Mohlo by to vypadat pak nějak takto:

19 bus.add_signal_receiver(hNoveZarizeni,    'DeviceAdded',
20                         ROZHRANI, SLUZBA, OBJEKT, arg0="zpráva")

Parse error: syntax error, unexpected T_CONSTANT_ENCAPSED_STRING in /web/htdocs2/wraithcz/home/www/python/data/sessions/sessions1.php on line 2