Bluez的使用与GDbus编程

本文详细介绍了D-Bus系统中的服务、对象、接口、方法和信号的概念,以及Bluezdaemonbluetoothd如何通过org.bluez服务提供功能,包括ProfileManager接口的使用。同时涵盖了gdbus编程和蓝牙调试技术,如ATCMD事件查看和蓝牙信号事件的监控。
摘要由CSDN通过智能技术生成

1. Dbus服务, 对象, 接口, 方法, 信号术语说明

服务 包含多个对象, 对象包含多个接口, 接口包含多个方法和信号.
方法通过dbus-send命令或编程来调用;
信号由接口发出, 我们编程接收并执行期望函数.

文心一言给出的说明:

D-Bus 是一种进程间通信 (IPC) 系统,它允许应用程序和服务在单个机器上或跨网络的不同机器之间进行通信。在 D-Bus
中,服务、对象和接口是核心概念,它们共同构成了 D-Bus 通信的基础。 服务 (Service)

服务是 D-Bus 上的一个实体,它提供了一个或多个对象,每个对象又有一个或多个接口。服务在 D-Bus 上通过其唯一的名称来标识,如
org.freedesktop.DBus 或
org.gnome.SessionManager。服务可以是本地的(在同一台机器上运行),也可以是远程的(在另一台机器上运行)。

服务通常是由某个应用程序或守护进程提供的,并且它们可以通过 D-Bus 公开其功能和状态,使得其他服务或应用程序可以与之交互。 对象
(Object)

在 D-Bus 中,对象代表了服务提供的一个功能集合。每个对象都有一个唯一的路径(以 / 开头的字符串),这个路径用于在 D-Bus
上标识该对象。例如,一个管理用户会话的服务可能会有一个路径为 /org/gnome/SessionManager 的对象。

对象通常包含多个接口,每个接口定义了一组相关的方法(用于执行操作)和信号(用于通知其他对象状态变化)。 接口 (Interface)

接口定义了对象提供的一组方法和信号。接口名称通常是以 . 分隔的字符串,如 org.freedesktop.DBus.Peer 或
org.gnome.SessionManager.Manager。接口定义了对象可以执行的操作以及可以接收的通知。

方法 是可以由其他对象调用的操作。例如,一个媒体播放器的接口可能有一个 Play 方法来开始播放媒体。

信号 是由对象发出的通知,用于告知其他对象某种状态变化或事件的发生。例如,一个媒体播放器的接口可能有一个 SongChanged
信号,每当播放的歌曲改变时就会发出。 示例

假设有一个名为 org.example.MediaPlayer 的服务,它提供了一个对象,路径为
/org/example/MediaPlayer,并且该对象实现了 org.example.MediaPlayer.Player
接口。这个接口可能定义了如下方法:

Play(uri:s): 开始播放指定 URI 的媒体。
Pause(): 暂停当前播放的媒体。
Stop(): 停止播放并返回初始状态。

同时,该接口也可能定义了如下信号:

SongChanged(title:s, artist:s): 当播放的歌曲改变时发出,携带新歌曲的标题和艺术家信息。
PlaybackStatusChanged(status:s): 当播放状态(如播放、暂停、停止)改变时发出。

其他服务或应用程序可以通过 D-Bus 与 org.example.MediaPlayer
服务进行通信,调用其方法或监听其信号,以实现媒体播放控制或状态监视的功能。

2.Bluez的daemon bluetoothd 提供服务org.bluez, 对象路径/org/bluez, /org/bluez/hci0,

对象路径/org/bluez/hci0 提供多个接口譬如org.bluez.ProfileManager1.
而接口org.bluez.ProfileManager1又提供RegisterProfile等方法.

可以通过dbus-send的方法调用接口提供的方法, 也可以直接使用gdbus库编程.

3.编程前, 我们需要了解bluez协议栈为我们提供了什么服务, 对象,接口,方法和信号.

可以通过下面的dbus-send命令获取信息.

3.1 获取dbus上已注册的服务

dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListActivatableNames

服务: org.bluez

在这里插入图片描述

3.2获知org.bluez服务提供的所有对象路径:

dbus-send --system --print-reply --type=method_call --dest=org.bluez / org.freedesktop.DBus.ObjectManager.GetManagedObjects

未扫描蓝牙设备前, org.bluez服务提供3个对象, 分别为:

/org/bluez; /org/bluez/test; /org/bluez/hci0;

若是成功扫描到了一些设备, 会多出许多这样的对象:

/org/bluez/hci0/dev_xx_xx; “xx_xx” 是蓝牙设备地址.
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用下面的dbus-send命令,查看/org/bluez; /org/bluez/test; /org/bluez/hci0; 对象提供接口, 以及接口提供的方法和信号.

3.3.1查看org.bluez服务下的对象”/” 下所有的接口的所有方法和信号

dbus-send --system --print-reply --dest=org.bluez --type=method_call / org.freedesktop.DBus.Introspectable.Introspect

/对象提供以下信息:

接口: org.freedesktop.DBus.Introspectable
	=> 方法: Introspect
接口: org.freedesktop.DBus.ObjectManager
	=> 方法: GetManagedObjects
	=> 信号: InterfacesAdded
	=> 信号: InterfacesRemoved

在这里插入图片描述

3.3.2查看org.bluez服务下的对象”/org/bluez” 下所有的接口的所有方法和信号

dbus-send --system --print-reply --dest=org.bluez --type=method_call /org/bluez  org.freedesktop.DBus.Introspectable.Introspect

/org/bluez对象提供以下信息:

接口: org.freedesktop.DBus.Introspectable
	=> 方法: Introspect
	
接口: org.bluez.AgentManager1
	=> 方法: RegisterAgent
	=> 方法: UnregisterAgent
	=> 方法: RequestDefaultAgent
	
接口: org.bluez.ProfileManager1
	=> 方法: RegisterProfile
	=> 方法: UnregisterProfile

在这里插入图片描述

3.3.3查看org.bluez服务下的对象”/org/bluez/test” 下所有的接口的所有方法和信号

dbus-send --system --print-reply --dest=org.bluez --type=method_call /org              
/bluez/test  org.freedesktop.DBus.Introspectable.Introspect

在这里插入图片描述

3.3.4查看org.bluez服务下的对象”/org/bluez/hci0” 下所有的接口的所有方法和信号

dbus-send --system --print-reply --dest=org.bluez --type=method_call /org/bluez/hci0  org.freedesktop.DBus.Introspectable.Introspect

4.接口提供的方法的参数说明

在执行以下命令时, 其实是调用/org/bluez/hci0对象的org.freedesktop.DBus.Introspectable接口的Introspect方法.

dbus-send --system --print-reply --dest=org.bluez --type=method_call /org/bluez/hci0  org.freedesktop.DBus.Introspectable.Introspect

Introspect方法以及其他方法的参数为:

<arg name="xml" type="s" direction="out"/>
  1. Name 为参数名, 在gdbus编程中似乎未使用到. 这里可以表明输出的信息格式为xml.
  2. Type 值为 “s”, 表明类型为string, 在gdbus编程中使用.
  3. Direction 值为 out, 表明该参数为输出参数. 在gdbus编程中, 我们给参数分配内存, 函数执行完成后, 可以打印参数内的信息. 在dbus-send命令的使用时, 就会在屏幕打印出来.
    值为in, 表明为输入参数, 需要我们填充参数内容.

5.dbus-send命令调用方法说明

我们可以直接问 文心一言, 譬如调用/org/bluez对象的org.bluez.ProfileManager1.RegisterProfile的方法.
在这里插入图片描述

我们可以这样提问:

dbus-send命令调用/org/bluez对象的org.bluez.ProfileManager1.RegisterProfile
RegisterProfile的xml描述为以下:
<method name="RegisterProfile">
<arg name="profile" type="o" direction="in"/>
<arg name="UUID" type="s" direction="in"/>
<arg name="options" type="a{sv}" direction="in"/>

文心一言回答调用方法为:

dbus-send --system --dest=org.bluez --type=method_call --print-reply \  
    /org/bluez/org.bluez.ProfileManager1 \  
    org.bluez.ProfileManager1.RegisterProfile \  
    string:"/path/to/your/profile/object" \  
    string:"your-uuid-here" \  
array:{"option1":"value1", "option2":"value2"}

在这里插入图片描述在这里插入图片描述

6.Gdbus编程模板:

6.1代理该接口

我认为是创建于接口的连接, 这样就可以调用接口的方法和注册接口发来的信号了.
使用 g_dbus_proxy_new_for_bus_sync()函数, 后续详细说明.

6.2参数的填充方式

可以使用g_variant_new()函数帮助我们填充输入参数.

以RemoveDevice方法为例:
RemoveDevice方法需要type为”o”的参数, “o”类型为对象路径类型.
在这里插入图片描述

函数实际使用:

g_variant_new((o),     /* 将参数类型填入 *//org/bluez/hci0/dev_xx_xx”  /* 将参数实际信息填入. */
);

以Get方法为例:
在这里插入图片描述

g_variant_new((ss),
	“接口名xxx”,
	“property的名字”,
);

参数name为输出参数, 不需要作用参数输入, 其值在保存在Get方法的返回值内, 打印返回值内的数据获取.

6.3 调用无参数的方法

以代理org.bluez.Adapter1接口, 调用StartDiscovery为例.
对应dbus-send命令为:

dbus-send --system --type=method_call --print-reply --dest=org.bluez "/org/bluez/hci0"  org.bluez.Adapter1.StartDiscovery
GError *error = NULL;
GDBusProxy *adapter_proxy = NULL;
GVariant *result;

adapter_proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, /* 使用系统总线 */
				    G_DBUS_PROXY_FLAGS_NONE,
				    NULL, /* GDBusInterfaceInfo */
				    “org.bluez”, /* 服务名 *//org/bluez/hci0”, /* 对象名 */
				    “org.bluez.Adapter1”, /* 接口名 */
				    NULL, /*cancellable */
				    &error);

/* 调用StartDiscovery方法 */
result = g_dbus_proxy_call_sync(adapter_proxy,
            “StartDiscovery”,  /* 方法名 */
            NULL,
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            NULL,
            &error);

/* 判断调用是否成功 */
if(result == NULL) {
        g_printerr("Error in scan: %s\n", error->message);
        g_error_free(error);
        return;
}

6.4 调用有输入参数的方法

以代理org.bluez.Adapter1接口, 调用RemoveDevice为例.

在这里插入图片描述

GError *error = NULL;
GDBusProxy *adapter_proxy = NULL;
GVariant *result;

adapter_proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, /* 使用系统总线 */
				    G_DBUS_PROXY_FLAGS_NONE,
				    NULL, /* GDBusInterfaceInfo */
				    “org.bluez”, /* 服务名 *//org/bluez/hci0”, /* 对象名 */
				    “org.bluez.Adapter1”, /* 接口名 */
				    NULL, /*cancellable */
				    &error);

/* 调用RemoveDevice方法, 移除设备地址为xx_xx的蓝牙设备 */
result = g_dbus_proxy_call_sync(adapter_proxy,
            “RemoveDevice”,  /* 方法名 */
            g_variant_new("(o)",/org/bluez/hci0/dev_xx_xx”),
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            NULL,
            &error);

/* 判断调用是否成功 */
if(result == NULL) {
        g_printerr("Error in scan: %s\n", error->message);
        g_error_free(error);
        return;
}

6.5 调用有输出参数的方法

以代理org.bluez.Adapter1接口, 调用GetDiscoveryFilters为例.
在这里插入图片描述

GError *error = NULL;
GDBusProxy *adapter_proxy = NULL;
GVariant *result;
gchar *result_str;

adapter_proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SYSTEM, /* 使用系统总线 */
				    G_DBUS_PROXY_FLAGS_NONE,
				    NULL, /* GDBusInterfaceInfo */
				    “org.bluez”, /* 服务名 *//org/bluez/hci0”, /* 对象名 */
				    “org.bluez.Adapter1”, /* 接口名 */
				    NULL, /*cancellable */
				    &error);

/* 调用GetDiscoveryFilters方法 */
result = g_dbus_proxy_call_sync(adapter_proxy,
            “GetDiscoveryFilters”,  /* 方法名 */
            NULL,
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            NULL,
            &error);

/* 判断调用是否成功 */
if(result == NULL) {
        g_printerr("Error in scan: %s\n", error->message);
        g_error_free(error);
        return;
}

/* 打印GetDiscoveryFilters方法的输出参数的内容 */
result_str = g_variant_print (result, TRUE);
g_print("result: %s\n", result_str);

6.6 注册信号
以”/org/bluez/hci0/dev_xx_xx”对象路径下的 org.bluez.Headset接口为例.
通过以下命令, 知道org.bluez.Headset提供的所有信号为:

dbus-send --system --print-reply --dest=org.bluez --type=method_call /org/bluez/hci0/dev_xx_xx  org.freedesktop.DBus.Introspectable.Introspect

在这里插入图片描述在这里插入图片描述

带Depercated信息的信号, 似乎是不可用的.
在这里插入图片描述

int main(void) 
{
	GDBusProxy *hs_proxy;
	GError *error = NULL;

	hs_proxy = g_dbus_proxy_new_for_bus_sync(
						    G_BUS_TYPE_SYSTEM, 
					    	G_DBUS_PROXY_FLAGS_NONE, 
					  	  NULL, 
						    “org.bluez”,/org/bluez/hci0/dev_xx_xx”, 
						    “org.bluez.Headset”, 
						    NULL, 
						    &error);
	/* 注册接收org.bluez.Headset发来的所有信号 */
	g_signal_connect(hs_proxy, 
			    "g-signal", 
			    G_CALLBACK(on_signal), /* 信号回调函数 */
	NULL);
}

static void on_signal(GDBusProxy *proxy,
           gchar      *sender_name,
           gchar      *signal_name,
           GVariant   *parameters,
           gpointer    user_data)
{
	gchar *parameters_str;
	const char *objpath;
	struct device_msg *dev_msg;

/* 获取代理的接口所属的对象路径 */
	objpath = g_dbus_proxy_get_object_path(proxy);
	g_print("%s: objpath: %s\n", __func__, objpath);

/* 获取信号提供的参数信息 */
	parameters_str = g_variant_print (parameters, TRUE);
/* 打印信号名, 和参数信息 */
	g_print (" *** Received Signal: %s: %s\n", signal_name, parameters_str);

	g_free (parameters_str);
}

7.蓝牙调试

7.1 蓝牙AT CMD事件查看

使用btmon命令, 可以查看蓝牙设备发送的AT CMD命令.
例如查看 蓝牙耳机的音量按键事件( AT+VGS=XX)
在这里插入图片描述

7.2 蓝牙发送的信号事件查看

使用bluetoothctl命令进行查看.
先使用bluetoothctl对蓝牙设备进行连接, 连接成功后, 其实就可以收到一个[SIGNAL] org.bluez.Headset.Connected信号.
这里也可以查看其他的信号.例如音量改变信号: [SIGNAL] org.bluez.Headset.SpeakerGainChanged

可以接收到什么信号, 查看方法上文已提及.
在这里插入图片描述

  • 17
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值