如何通过dbus开发bluez(c语言版)
前言:本篇文章只适用于初步学习bluez的人,如果对dbus不了解的话,建议先看python是如何操作bluez,哪怕不会python也可以将python当成伪代码去了解一下。
1.简介
如何通过c语言去开发bluez,截至到我写这篇文章为止,百度,谷歌搜下来,估计就csdn一个写套例子放在csdn上,但我没会员,也就没下来看了。这里我提供个简单的蓝牙扫描例子,以此来敲砖引玉。
2.环境
dbus:这里使用gdbus(dbus-glib是比较老的库,现在都替换成gdbus,此外还有个sdbus,但考虑到用dbus肯定要用上常见的数据结构,gdbus位于glib里,glib就能解决常用数据结构问题,不少linux发行版都自带glib,所以我个人还是比较推荐gdbus)
编译器:我用的是yocto生成的交叉编译工具链,用其他编译器的话,把"{CC} ${LDFLAGS}" 替换成对应编译器应该就可以了
3.源码
test-gdbus.c
#include <stdio.h>
#include <glib.h>
#include <gio/gio.h>
GDBusConnection *conn;
static void objectManager_handler (GDBusProxy *proxy,
gchar *sender_name,
gchar *signal_name,
GVariant *params,
gpointer user_data);
static gboolean properties_changed(GDBusProxy *pProxy, GVariant *params, gpointer user_data)
{
const gchar *interface_name;
GVariantIter *changed_properties;
GVariantIter *invalidated_properties;
g_variant_get(params, "(sa{sv}as)", &interface_name, &changed_properties, &invalidated_properties);
printf("properties_changed interface_name: %s\n", interface_name);
const gchar *property_name;
GVariant *property_value;
while (g_variant_iter_next (changed_properties, "{sv}", &property_name, &property_value))
{
printf(" changed property_name: %s\n", property_name);
}
while (g_variant_iter_next (invalidated_properties, "s", &property_name))
{
printf(" invalidated property_name: %s\n", property_name);
}
}
static void properties_handler (GDBusProxy *proxy,
gchar *sender_name,
gchar *signal_name,
GVariant *params,
gpointer user_data) {
// printf("properties_handler signal_name:%s\n", signal_name);
if (g_strcmp0(signal_name, "PropertiesChanged") == 0) {
// ignore all irrelevant signals
properties_changed(proxy, params, user_data);
return;
}
}
static gboolean interfaces_added(GDBusProxy *pProxy, GVariant *params, gpointer user_data)
{
const gchar *path;
const gchar *interface_name;
GVariantIter *ifaces_and_properties;
GVariantIter *properties;
GError *error = NULL;
g_variant_get(params, "(oa{sa{sv}})", &path, &ifaces_and_properties);
GDBusProxy *properties1;
properties1 = g_dbus_proxy_new_sync(conn,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.bluez",
path,
"org.freedesktop.DBus.Properties",
NULL,
&error);
if(error != NULL) {
printf("error\n");
return 1;
}
// g_signal_connect(properties, "g-properties-changed", G_CALLBACK(properties_handler), NULL);
g_signal_connect(properties1, "g-signal", G_CALLBACK(properties_handler), NULL);
GDBusProxy *device1;
device1 = g_dbus_proxy_new_sync(conn,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.bluez",
path,
"org.bluez.Device1",
NULL,
&error);
if(error != NULL) {
printf("error\n");
return 1;
}
GVariant *rssi = NULL;
gint16 rssi_num;
rssi = g_dbus_proxy_get_cached_property(device1, "RSSI");
if(rssi != NULL) {
g_variant_get(rssi, "n", &rssi_num);
printf("interfaces_added: odject path is \"%s\", RSSI: %d\n", path, rssi_num);
} else {
printf("interfaces_added: odject path is \"%s\"\n", path);
}
while (g_variant_iter_next (ifaces_and_properties, "{sa{sv}}", &interface_name, &properties))
{
printf(" interface_name: %s\n", interface_name);
}
return TRUE;
}
static void objectManager_handler (GDBusProxy *proxy,
gchar *sender_name,
gchar *signal_name,
GVariant *params,
gpointer user_data) {
printf("objectManager_handler signal_name:%s\n", signal_name);
if (g_strcmp0(signal_name, "InterfacesAdded") == 0) {
// ignore all irrelevant signals
interfaces_added(proxy, params, user_data);
return;
}
}
int main()
{
GMainLoop *mainloop;
int rc = 0;
GDBusProxy *objectManager;
GDBusProxy *adapter1;
GError *error = NULL;
GVariant *result;
//申请创建主循环
mainloop = g_main_loop_new(NULL, FALSE);
conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
if(error != NULL)
return 1;
objectManager = g_dbus_proxy_new_sync(conn,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.bluez",
"/",
"org.freedesktop.DBus.ObjectManager",
NULL,
&error);
if(error != NULL)
return 1;
g_signal_connect(objectManager, "g-signal", G_CALLBACK(objectManager_handler), NULL);
adapter1 = g_dbus_proxy_new_sync(conn,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.bluez",
"/org/bluez/hci0",
"org.bluez.Adapter1",
NULL,
&error);
if(error != NULL)
return 1;
error = NULL;
result = g_dbus_proxy_call_sync(adapter1,
"StartDiscovery",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if(error != NULL)
return 1;
g_main_loop_run(mainloop);
if(objectManager)
g_object_unref(objectManager);
if(adapter1)
g_object_unref(adapter1);
if(conn)
g_object_unref(conn);
if(error)
g_error_free(error);
}
编译命令(需要确认下有没有对应的库):
${CC} ${LDFLAGS} test-gdbus.c `pkg-config --cflags --libs gio-2.0 dbus-1 gobject-2.0 glib-2.0` -I /usr/include/glib-2.0 -I /usr/lib64/glib-2.0/include -o test-gdbus
执行:
./test-gdbus
回出现一下类型 log:
objectManager_handler signal_name:InterfacesAdded
interfaces_added: odject path is "/org/bluez/hci0/dev_54_1D_DA_9F_89_68", RSSI: -92
interface_name: org.freedesktop.DBus.Introspectable
interface_name: org.bluez.Device1
interface_name: org.freedesktop.DBus.Properties
properties_changed interface_name: org.bluez.Device1
changed property_name: RSSI
4.分析
4.1 main函数分析
大部分为固定流程
以下为启动扫描(这里值考虑用gdbus的同步函数,如果对dbus的异步函数感兴趣可以自行去了解相关资料):
adapter1 = g_dbus_proxy_new_sync(conn,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.bluez",
"/org/bluez/hci0",
"org.bluez.Adapter1",
NULL,
&error);
if(error != NULL)
return 1;
error = NULL;
result = g_dbus_proxy_call_sync(adapter1,
"StartDiscovery",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if(error != NULL)
return 1;
g_dbus_proxy_new_sync:用来获取接口,里面的"org.bluez", “/org/bluez/hci0”, “org.bluez.Adapter1”, 如果已经对dbus有了解的话,应该很容易就清楚是什么了。
g_dbus_proxy_call_sync:调用函数,由于StartDiscovery,没有入参和出参,所以入参是NULL,result也没进行处理
以下为处理InterfacesAdded:
objectManager = g_dbus_proxy_new_sync(conn,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.bluez",
"/",
"org.freedesktop.DBus.ObjectManager",
NULL,
&error);
if(error != NULL)
return 1;
g_signal_connect(objectManager, "g-signal", G_CALLBACK(objectManager_handler), NULL);
g_signal_connect:用来设置具体的信号和对应的回调
如果是看过我写的python篇,应该会清楚这里要连接的是InterfacesAdded信号,但这里却连接了g-signal,这是由于现在这种写法由于没有dbus通信对应具体的xml,信号得先通过g-signal,才能做进一步分析。可以参考https://docs.gtk.org/gio/signal.DBusProxy.g-signal.html。
如果有编写有对应xml文件,gdbus有工具协助生成一套封装好的api,但这里就不做展开
4.2 信号解析
先看具体的 objectManager_handler 函数
static void objectManager_handler (GDBusProxy *proxy,
gchar *sender_name,
gchar *signal_name,
GVariant *params,
gpointer user_data) {
printf("objectManager_handler signal_name:%s\n", signal_name);
if (g_strcmp0(signal_name, "InterfacesAdded") == 0) {
// ignore all irrelevant signals
interfaces_added(proxy, params, user_data);
return;
}
}
objectManager_handler 函数的入参是固定的 其对应g-signal的规定,详情可以通过gdbus官网了解。
在这个函数里可以看到有比较InterfacesAdded,就是用来识别InterfacesAdded信号,另外这里InterfacesAdded信号输出的参数也全部被整合成一个GVariant变量,后续需要拆解GVariant变量来获取具体的参数。
interfaces_added 函数实现, 先看第一步解析信号传回来的数据
g_variant_get(params, "(oa{sa{sv}})", &path, &ifaces_and_properties);
看看d-feet里显示的输出变量
这里再引入dbus的变量表,因为dbus的参数解析用
变量名 | 解析 |
---|---|
a | ARRAY 数组 |
b | BOOLEAN 布尔值 |
d | DOUBLE IEEE 754双精度浮点数 |
g | SIGNATURE 类型签名 |
i | INT32 32位有符号整数 |
n | INT16 16位有符号整数 |
o | OBJECT_PATH 对象路径 |
q | UINT16 16位无符号整数 |
s | STRING 零结尾的UTF-8字符串 |
t | UINT64 64位无符号整数 |
u | UINT32 32位无符号整数 |
v | VARIANT 可以放任意数据类型的容器,数据中包含类型信息。例如glib中的GValue。 |
x | INT64 64位有符号整数 |
y | BYTE 8位无符号整数 |
() | 定义结构时使用。例如"(i(ii))" |
{} | 定义键-值对时使用。例如"a{us}" |
这里重新再对比下InterfacesAdded信号的入参,以及处理的参数类型
"(oa{sa{sv}})"
(Object Path, Dict of {String, Dict of {String, Variant}} objects)
这两个是一一对应的,拆解后获取的
path:对应Object Path
ifaces_and_properties:对应Dict of {String, Dict of {String, Variant}
interfaces_added 函数实现, 注册PropertiesChanged信号
GDBusProxy *properties1;
properties1 = g_dbus_proxy_new_sync(conn,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.bluez",
path,
"org.freedesktop.DBus.Properties",
NULL,
&error);
if(error != NULL) {
printf("error\n");
return 1;
}
// g_signal_connect(properties, "g-properties-changed", G_CALLBACK(properties_handler), NULL);
g_signal_connect(properties1, "g-signal", G_CALLBACK(properties_handler), NULL);
注册PropertiesChanged信号这里的逻辑和InterfacesAdded类似。另外如果有人看过我写的python版dbus控制bluez会发现,这里的PropertiesChanged信号是在InterfacesAdded信号处理中进行注册,这样是为每一个扫描到的蓝牙设备添加PropertiesChanged信号处理,python版那边的处理方法虽然不同,但也实现了一样的效果。(这里的处理方法我是参考bluetoothctl源码实现的,bluetoothctl源码位于client/)
获取属性
GDBusProxy *device1;
device1 = g_dbus_proxy_new_sync(conn,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.bluez",
path,
"org.bluez.Device1",
NULL,
&error);
if(error != NULL) {
printf("error\n");
return 1;
}
GVariant *rssi = NULL;
gint16 rssi_num;
rssi = g_dbus_proxy_get_cached_property(device1, "RSSI");
if(rssi != NULL) {
g_variant_get(rssi, "n", &rssi_num);
printf("interfaces_added: odject path is \"%s\", RSSI: %d\n", path, rssi_num);
} else {
printf("interfaces_added: odject path is \"%s\"\n", path);
}
C语言获取属性还是麻烦了点,对比python,python都直接把整个结构解析好了,直接用就可以。下面贴出d-feet里显示的属性,方便理解
最后是如何解析字典类变量
g_variant_get(params, "(oa{sa{sv}})", &path, &ifaces_and_properties);
while (g_variant_iter_next (ifaces_and_properties, "{sa{sv}}", &interface_name, &properties))
{
printf(" interface_name: %s\n", interface_name);
}
看一下应该很容易就明白了。
5.结语
bluez要如何通过dbus操作,通过上面的说明应该已经很清晰,另外的一些blues使用逻辑可以参考bluetoothctl源码和test/里面的python代码。例如:bluetoothctl里还处理了InterfacesRemoved信号,但在test-discovery中却没有处理,这样可能会导致已经消失了的蓝牙还显示存在。所以关于蓝牙的一些细致操作还是要再多看看的。