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"/>
- Name 为参数名, 在gdbus编程中似乎未使用到. 这里可以表明输出的信息格式为xml.
- Type 值为 “s”, 表明类型为string, 在gdbus编程中使用.
- 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
可以接收到什么信号, 查看方法上文已提及.