dbus-python指南

怎甘沉沦 提交于 2019-12-06 07:41:11

菜鸟学dbus-python,翻译dbus-python指南,错误之处请在所难免,请诸位不吝赐教,多多指正!查看英文原版请点这里

连接总线(Connecting to the Bus)

使用D-Bus的应用程序通常都会连接到一个总线服务(bus daemon)上,这个总线服务在程序之间传递消息。为了使用D-Bus,你需要创建一个Bus对象来代表到总线服务的连接。
一般来说,我们感兴趣的总线(bus)服务有2种:会话总线和系统总线。

每个登陆的用户所属的会话都应该有一个会话总线(session bus),会话总线只在本会话内有效。它用于桌面程序间的通信。可以通过创建一个SessionBus的对象连接到会话总线:

import dbus
session_bus = dbus.SessionBus()

系统总线是全局的,通常在系统启动的时候就被加载了;它用于与系统服务通信,例如udev, NetworkManager和Hardware Abstraction Layer daemon(hald)。可以通过创建一个SystemBus对象来连接到系统总线。

import dbus
system_bus = dbus.SystemBus()

当然,你可以在程序里两个总线都连接。
在一些特殊的情况下,你可能不使用默认的总线(Bus),或者根本就不连接到总线上,这些可以通过dbus-python 0.81.0中新添加的API来实现。但是这里不做介绍,他们是别的指南中的一个主题。

方法调用(Making method calls)

D-Bus应用程序可以export对象给其他应用程序使用。为了使用其他应用程序提供的对象,你需要知道:

  • 总线名(bus name)。总线名用来标识你想与哪个应用程序通信。我们通常使用一个众所周知的名字(well-known name)来识别一个程序。这个众所周知的名字是一个反域名的点分式字符串,例如org.freedesktop.NetworkManager或者com.example.WordProcessor。
  • 对象路径(object path)。应用程序可以export很多对象。例如example.com的文档处理程序可能提供一个对象代表文档处理程序自身,并且为每个打开的文档窗口创建一个对象,还可能为文档中的每一个段落创建一个对象。
    为了标识要与哪一个对象通信,你可以使用对象路径来指定。对象路径是一个由反斜线作为分隔的字符串,类似Linux系统下的文件名。例如,example.com的文档处理程序可能提供一个对象路径为/的对象代表程序自身,还可能提供对象路径为/documents/123和/documents/345的对象代表打开的文档窗口。
    正如你所期望的那样,你对远端对象做的最主要的事情就是调用它们的方法。在Python中,方法可以有参数,同时它们可能返回一个或更多的返回值。

    代理对象(proxy objects)

    为了与一个远端的对象通信,我们使用代理对象。代理对象是一个Python对象,它是远端对象的一个代理或替身-当调用代理对象的一个方法时,会导致dbus-python对远端对象的方法调用,并将远端对象的返回值作为代理对象方法调用的返回值。
    可以对Bus调用get_object方法来获取代理对象。例如,NetworkManager有一个众所周知的名称org.freedesktop.NetworkManager, NetworkManager export一个对象路径为/org/freedesktop/NetworkManager的对象。NetworkManager为每一个网络接口(network interface)都在对象路径下创建一个对象,例如,/org/freedesktop/NetworkManager/Devices/eth0。你可以像这样获取一个代理对象来代理eth0:

    import dbus
    bus = dbus.SystemBus()
    proxy = bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Devices/eth0')
    
    #proxy 是一个dbus.proxies.ProxyObject类型的对象
    

    接口和方法(Interfaces and methods)

    D-Bus使用接口(interfaces)给方法(methods)提供命名空间的机制。一个接口(interface)就是一组相关的方法和信号(后面再介绍信号),接口名字是一个反域名的点分式字符串。例如,每一个代表一个网络接口的NetworkManager对象都实现了接口org.freedesktop.NetworkManager.Devices,在这个接口中有一个方法是gerProperties。
    要调用一个方法,就在代理对象上调用相同的方法,并通过dbus_interface关键字参数将接口名传入:

    import dbus
    bus = dbus.SystemBus()
    eth0 = bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Devices/eth0')
    props = eth0.getProperties(dbus_interface='org.freedesktop.NetworkManager.Devices')
    
    #props 是一个属性元组,元组的第一项是对象路径。
    

    简便起见,如果你要在同一个接口上调用多个方法时,可以先构造一个dbus.Interface的对象,通过这个对象调用方法,这样就可以避免每次调用方法都要传入接口名的麻烦:

    import dbus
    bus = dbus.SystemBus()
    eth0 = bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Devices/eth0')
    eth0_dev_iface = dbus.Interface(eth0, dbus_interface='org.freedesktop.NetworkManager.Devices')
    props = dbus_dev_iface.getProperties()
    
    #props 同样是一个属性元组,元组的第一项是对象路径。
    

See also

可以参考examples/example-client.py中的例子。在运行它之前,你需要在后台或者另一个shell上运行examples/example-service.py

数据类型(Data types)

与Python不同,D-Bus使用静态类型-每个方法都有一个标识来代表其参数的类型,其他类型的参数是不被接受的。
D-Bus有一个内省机制(introspection mechanism),dbus-python通过这个机制来尝试发现正确的参数类型。如果尝试成功,Python类型会被自动转化为正确的D-Bus数据类型,如果可以;如果类型不恰当,会报TypeError错误。
如果内省失败(或者参数类型可变-详见下文),你必须提供正确的参数类型。dbus-python根据D-Bus的数据类型提供了相应的Python类型,一些Python类型可以自动的转化为D-Bus数据类型。如果你使用这些类型之外的类型时,会TypError出错,提示你dbus-python不能猜出D-Bus的标识。

基本类型(Basic types)

以下是支持的基本数据类型。

Python types converted to D-Bus tyep notes
D-Bus proxy object ObjectPath(signature’o’) (+)
dbus.Interface ObjectPath(signature’o’) (+)
dbus.service.service.Object ObjectPath(signature’o’) (+)
dbus.Boolean Boolean(signature’b’) a subclass of int
dbus.Byte byte(signature’y’) a subclass of int
dbus.Int16 16-bit signed integer(‘n’) a subclass of int
dbus.Int32 32-bit signed integer(‘i’) a subclass of int
dbus.Int64 64-bit signed integer(‘x’) (*)
dbus.UInt16 16-bit unsigned integer(‘q’) a subclass of int
dbus.UInt32 32-bit unsigned integer(‘u’) (*)_
dbus.UInt64 64-bit unsigned integer(‘t’) (*)_
dbus.Double double-precision float(‘d’) a subclass of float
dbus.ObjectPath object path(‘o’) a subclass of str
dbus.Signature signature(‘g’) a subclass of str
dbus.String string(’s’) a subclass of unicode
dbus.UTF8String string(’s’) a subclass of str
bool Boolean(‘b’)
int or subclass 32-bit signed integer(‘i’)
long or subclass 64-bit signed integer(‘x’)
float or subclass double-precision float(‘d’)
str or subclass string(’s’) must be valid UTF-8
unicode or subclass string(’s’)

标记(*)的类型,在不同的平台上可能是int的子类或者long的子类。
(+):D-Bus proxy objects, exported D-Bus service objects 以及其他任何拥有dbus_object_path属性(必须为字符串,可以转化为对象路径)的对象。这在你使用dbus-python编写面向对象的API时,可能对你有帮助。

基本类型转换(Basic types conversions)

如果内省成功,dbus-python还接受:

  • 对于Boolean参数,任何对象(经由int(bool(…))转化)
  • 对于byte参数,a single-character string,即,单字字符串(经由ord()转化)
  • 对于byte和integer参数,任何integer(必须在正确的区间)
  • 对于object-path和signature参数,任何str或者unicode的子类(值必须遵循正确的语法)。

容器类型(Container types)

D-Bus支持4种容器类型:array(包含同种类型数据的可变长度队列), struct(一个固定长度队列,它的成员可能是不同的数据类型),dictionary(从同一基本类型到同一类型的映射),variant(一种可以存储任何D-Bus数据类型的容器,包含另一variant在内)。

Array的代表是Python的list或者dbus.Array(list的一个子类)。发送一个array时,如果内省标识(introspected signature)可用,则使用内省标识;否则,如果传递了signature关键字参数给array的构造函数,则使用传递的signature来作为array内容的signature;如果内省失败,也没有给array的构造函数传递signature关键字参数,则dbus-python将根据array的第一个元素来猜测其signature.

一个array的signature格式为’ax’,其中的’x’代表array元素的signature。例如,你可以用’as’表示字符串array(array of strings),或者用’a(ii)’表示结构体数组(array of structs each containing two 32-bit integer),这个array的元素是有2个32-bit整数组成的结构体。

还有一种类型dbus.ByteArray,它是str的一个子类。这种类型被广泛用于代表一个D-Bus的字节数组(signature ‘ay’)。

Struct的代表是Python的tuple或者dbus.Struct(tuple的一个子类)。当发送一个struct时,如果内省的signature可用,则使用内省的signature;否则,如果传递signature关键字参数给Struct(指南中使用的的Array,个人感觉是写错了,不是是否理解有误)的构造函数,则使用此传递的signature作为struct内容的signature;否则dbus-python将通过struct(同样,指南原文使用的是Array)的第一个元素来猜测signature(这里个人有一点小疑问,struct的成员数据类型是不一定相同的,这个要怎么根据第一个元素猜?)。

一个struct的signature是用一个元括号将其成员的signature括起来。例如’(is)’是一个struct的signature,这个struct包含了一个32-bit的integer成员和一个string类型成员。

Dictionary的代表是Python的Dictionary或者dbus.Dictionary(一个dict的子类)。当发送一个dictionary时,如果内省的signature可用,则使用内省signature;否则,如果给Dictionary的构造函数传递了signature关键字参数,则使用传递的signature作为dictionary内部的key和value的signature;否则,dbus-python会根据dict中随意的一个item来猜测其signature。

dictionary的signature的格式为’a{xy}’,其中,’x’是dictionary中key的signature(它不能是容器的类型),’y‘是dictionary中的value的signature。例如,’a{s(ii)}’是一个dictionary的signature,这个dictionary的key是一个字符串,value是一个由2个32-bit的整数组成的结构体。

variant的代表是在D-Bus的任意一个数据类型上,通过其构造函数将关键字参数variant_level的值设置为一个大于0的数。(variant_level 1表示一个包含一些其他数据类型(不能是variant类型)的成员variant。variant_level 2 表示一个包含另一个variant(被包含的variant是一个包含除了variant类型之外的其他数据成员的variant)成员的variant,等等)。如果将一个非variant(non-variant)数据传递给一个内省要求为variant的参数,则,这个非variant数据会被自动封装为variant类型。

variant的signature是’v’。

返回值和byte_arrays和utf8_strings选项

如果一个D-Bus方法没有返回值,则Python的代理方法返回None。

如果一个D-Bus方法有一个返回值,则Python代理方法会根据dbus.Types的相应类型返回那个数值-默认情况下,如果方法返回dbus.String(Unicode的一个子类)类型,则代理方法返回string。如果方法返回值是由dbus.Byte组成的dbus.Array,则Python代理方法返回array。

如果一个D-Bus方法返回多个返回值,则Python代理方法返回一个包含这些数值的tuple。

如果你想让返回的string类型是dbus.UTF8String(str的一个子类),需要给python的代理方法传递关键字参数utf8_string=True。

如果你想让byte array数值以dbus.ByteArray(同样也是str的子类,在实践中,这个类型经常是你最需要的)的类型返回,需要给python代理方法传递关键字参数byte_arrays=True。

异步方法调用(Making asynchronous method calls)

异步(非阻塞)方法调用允许多个方法调用同时进行,并且允许你在等待返回结果之前做其他事情。要使用异步方法调用,你首先需要一个事件循环或者“主循环”(main loop)。

设置一个事件循环(Setting up an event loop)

当前,dbus-python唯一支持的主循环(main loop)就是GLib。
dbus-python有一个全局默认的主循环,这是使用这个功能的最简便方法。要把GLib设置为默认主循环可以通过:

from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)

这些操作必须要在连接到bus服务之前完成。
可以通过pygi开始运行主循环:

from gi.repository import GLib
loop = GLib.MainLoop()
loop.run()

当loop.run()正在执行时,GLib会在适当的时候调用你的回调函数。要停止事件循环,调用loop.quit()。
你也可以通过在连接Bus服务时,通过传递一个main loop给Bus的构造函数,这样也可以设置主循环:

import dbus
from dbus.mainloop.glib import DBusGMainLoop
dbus_loop = DBusGMainLoop()
bus = dbus.SessionBus(mainloop=dbus_loop)

这个功能不是很有用,除非我们能够支持多个主循环。

向后兼容:dbus.glib

在dbus-python 0.80以前的版本, 设置GLib作为默认主循环的方式是:

import dbus.glib

执行以上import语句会自动加载GLib的主循环并将其作为默认设置。这中做法由于太不明显已经被抛弃。但是知道这个事情对你要编写或者理解以前版本的代码还是有用的。

Qt主循环

PyQt V4.2和其以后版本支持将dbus-python整合到Qt事件循环中。要把D-Bus连接到Qt的主循环中,掉用dbus.mainloop.qt.DBusQtMainLoop
代替dbus.mainloop.glib.DBusGMainLoop即可。除此以外,Qt循环和GLib循环的使用方式一样。

异步调用(Making asynchronous calls)

要做异步调用,需要传递2个可调用的关键字参数reply_handler和error_handler给代理方法(代理对象的方法)。代理方法会理解返回None。一段时间以后,在事件循环的运行过程中,会发生以下两种情况之一:

  • reply_handler以method的返回值作为参数被调用;或者
  • error_handler以一个代表远端异常的DBusException实例为参数被调用。

See also

examples/example-async-client.py异步调用了examples/example-service.py提供的服务,这些服务会返回一个值或者异常。与examples/example-client.py一样,你也需要先在后台或者另一个shell运行examples/example-service.py

接收信号(Receiving signals)

要接收信号,Bus需要连接到一个事件循环上。只有当事件循环运行时,信号才能被接收。

信号匹配(Signal matching)

要响应信号,你可以在Bus对象上使用add_signal_receiver方法。当接收到匹配的信号后,会调用安排好的回调函数。add_signal_receiver有以下参数:

  • a callable(the handler_function),当接收到信号时会调用这个函数,它的参数将作为信号的参数。
  • 信号名,signal_name:默认为None,匹配所有名字
  • D-Bus接口,dbus_interface:默认为None,匹配所有接口
  • 发送端的众所周知名或唯一名,bus_name:默认None,匹配所有发送者。众所周知名匹配当前这个周知名的拥有者。
  • 发送端的对象路径,path:默认为None,匹配所有对象路径。
    add_signal_receiver 也有两个关键字参数utf8_strings和byte_arrays,它们会影响调用handler函数时使用的数据类型。它们对于代理对象也会产生同样的影响。
    add_signal_receiver返回一个SignalMatch对象,这个对象唯一有用的公共API是无参数的remove方法,调用这个方法会从连接中删除信号的匹配。

从一个信号获取更多信息(Getting more information from a signal)

你也可以安排更多的信息传递给handler函数。如果你传递关键字参数sender_keyword, destination_keyword, interface_keyword, member_keyword或者path_keyword给connect_to_signal方法,则信号的相应部分的信息会作为关键字参数传递给handler函数。例如:

def handler(sender=None):
    print "got signal from %r" % sender
iface.connect_to_signal("Hello", handler, sender_keyword='sender')

当接收到来自com.example.Foo的无参数信号Hello时,handler函数会被调用,并且以sender=’com.example.Foo’为参数。

字符串参数匹配(String argument matching)

如果关键字参数的格式是argN,N是一个小的非负数,并且它们的值必须是unicode或UTF-8格式的字符串。此时,只有当接收到的信号的参数是这个关键字参数字符串时,才会调用handler函数。

从代理对象接收信号(Receiving signals from a proxy object)

代理对象有一个特殊的connect_to_signal方法,它安排一个可调用函数,当代理对象接收到来自其对应的远端的信号时,就会调用这个可调用函数。代理对象的connect_to_signal的参数有:

  • 信号名称。
  • 一个可调用函数(the handler function),当接收到信号时被事件循环调用-它的参数将作为信号的参数。
  • handler函数,一个可调用函数:与add_signal_receiver一样
  • 关键字参数 dbus_interface,指定接口名称。
    dbus.Interface对象也有一个类似的connect_to_signal方法,但是它不再需要关键字参数dbus_interface,因为接口名信息已经知道了。

尽管可以你可以创建代理对象并启用监听信号的服务,但是我们不应该仅仅为了监听信号就专门创建代理对象,如果你已经创建了一个代理对象用于方法调用,那么你也可以很方便的用它来匹配信号。

See also

examples/signal-recipient.py接收信号-它示范了常用信号的匹配以及connect_to_signal的使用。在运行这个脚本之前,你需要在后台或者另一个shell里运行examples/signal-emitter.py

Exporting 对象(Exporting objects)

对象被exported后可以很方便的让其他程序通过D-Bus定位到。所有dbus.service.Object的子类都会自动被exported。
要export对象,Bus需要连接到一个事件循环。只有在事件循环运行时,export方法才会被调用,队列中的信号才会被发送。

从dbus.services.Object继承

要把一个对象export到Bus上,只要子类化dbus.service.Object就可以了。需要将一个BusName/Bus object,以及对象路径传递给对象的构造函数。例如:

class Example(dbus.service.Object):
    def __init__(self, object_path):
        dbus.service.Object.__init__(self, dbus.SessionBus(), path)

这个对象会自动支持introspection, 但是没有其它任何有用的功能。要完善它,我们需要export一些方法和信号。

使用dbus.service.method Exporting方法(Exporting methods with dbus.service.method)

可以使用dbus.service.method装饰器(python用于修饰函数)来export一个方法。例如:

class Example(dbus.service.Object):
    def __init__(self, object_path):
        dbus.service.Object.__init__(self, dbus.SessionBus(), path)

    @dbus.service.method(dbus_interface='com.example.Sample', in_signature='v', out_signature='s')
    def StringifyVariant(self, variant):
        return str(variant)

in_signature和out_signature是D-Bus的标识字符串,在数据类型章节介绍了。
你也可以传递关键字参数byte_arrays和utf8_strings,这会影响到在D-Bus调用装饰方法时,传递给装饰方法的参数的类型。这两个关键字参数也对代理方法的返回值产生同样的影响。

找出调用者的bus name(Finding out the caller’s bus name)

装饰方法接受sender_keyword关键字参数。如果你把这个关键字参数设置为一个字符串,则发送者的唯一名会被作为那个名字的关键字参数传递给装饰方法:

class Example(dbus.service.Object):
    def __init__(self, object_path):
        dbus.service.Object.__init__(self, dbus.SessionBus(), path)

    @dbus.service.method(dbus_interface='com.example.Sample', in_signature='', out_signature='s', sender_keyword='sender')
    def SayHello(self, sender=None):
        return 'Hello, %s!' % sender
        # ->something like 'Hello, :1.1!'

异步方法实现(Asynchronous method implementations)

例子参考examples/example-async-service.py。(这个例子服务器上还没有)

使用dbus.service.signal发送信号(Emitting signals with dbus.service.signal)

要export一个信号,可以使用装饰符dbus.service.signal;要发送那个信号,调用装饰方法。通常,装饰方法也可以包含调用时运行的代码(不太理解,感觉是废话),例如:

class Example(dbus.service.Object):
    def __init__(self, object_path):
        dbus.service.Object.__init__(self, dbus.SessionBus(), path)

    @dbus.service.signal(dbus_interface='com.example.Sample', signature='us')
    def NumberOfBottlesChanged(self, number, content):
        print "%d bottles of %s on the wall" % (number, content)

e = Example('/bottle-counter')
e.NumberOfBottlesChanged(100, 'beer')
# -> emits com.example.Sample.NumberOfBottlesChanged(100, 'beer')
# and prints "100 bottles of beer on the wall"

例子(Example)

examples/example-signal-emitter.py的某个方法被调用时,它会根据需要发送一些信号。(事实上,当一些内部状态发生变化时你会发送一个信号。这些状态的变化可能是D-Bus的方法调用引起的也可能不是由由D-Bus的方法调用引起的。)

Licence for this document

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!