如何通过dbus开发bluez(c语言版)

如何通过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的参数解析用

变量名解析
aARRAY 数组
bBOOLEAN 布尔值
dDOUBLE IEEE 754双精度浮点数
gSIGNATURE 类型签名
iINT32 32位有符号整数
nINT16 16位有符号整数
oOBJECT_PATH 对象路径
qUINT16 16位无符号整数
sSTRING 零结尾的UTF-8字符串
tUINT64 64位无符号整数
uUINT32 32位无符号整数
vVARIANT 可以放任意数据类型的容器,数据中包含类型信息。例如glib中的GValue。
xINT64 64位有符号整数
yBYTE 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中却没有处理,这样可能会导致已经消失了的蓝牙还显示存在。所以关于蓝牙的一些细致操作还是要再多看看的。
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值