C语言 BLE低功耗蓝牙 连接初探

前言

bluez编译完后会生成很多命令行工具,比如gatttool、hcitool、bluetoothctl等,bluetoothctl的生成需要在configure的时候把–disable-test去掉。这些工具可以用来在linux环境下与ble设备进行调试,但是本人需要的是可用的c语言api,如果你只是开发经典蓝牙,那么恭喜你,交叉编译完后的的api足够用了;但是低功耗蓝牙用的C接口是没有的,下面就是我的趟坑过程,希望对大家有所帮助。

虽然bluez并没有给c提供直接可用的ble接口,但是通过分析源码我们可以找到更下层实现方式。

思路:

1.先熟悉各个工具,用工具连接ble设备实现服务、关键字的读写,经测试可正常使用的工具是hcitool(ble扫描),gatttool、bluetoothctl、btgatt-client;

2.分析以上工具的源码,提取可用的api,将bluez源码重新编译成为我所用的api。

这件事已经有老外实现了,但是我在交叉编译过程中遇到很多问题未解决,所以并未再继续,有兴趣的可以看看:

https://github.com/labapart/gattlib

扫描

扫描参考hcitool工具源码:/tool/hcitool.c

扫描的api再hci_lib.h中已经定义了,也包含在libbluetooth.so中,可以直接使用。

static void cmd_lescan(int dev_id, int argc, char **argv)
{
	int err, opt, dd;
	uint8_t own_type = LE_PUBLIC_ADDRESS;
	uint8_t scan_type = 0x01;
	uint8_t filter_type = 0;
	uint8_t filter_policy = 0x00;
	uint16_t interval = htobs(0x0010);
	uint16_t window = htobs(0x0010);
	uint8_t filter_dup = 0x01;

	for_each_opt(opt, lescan_options, NULL) {
		switch (opt) {
		case 's':
			own_type = LE_RANDOM_ADDRESS;
			break;
		case 'p':
			own_type = LE_RANDOM_ADDRESS;
			break;
		case 'P':
			scan_type = 0x00; /* Passive */
			break;
		case 'w':
			filter_policy = 0x01; /* Whitelist */
			break;
		case 'd':
			filter_type = optarg[0];
			if (filter_type != 'g' && filter_type != 'l') {
				fprintf(stderr, "Unknown discovery procedure\n");
				exit(1);
			}

			interval = htobs(0x0012);
			window = htobs(0x0012);
			break;
		case 'D':
			filter_dup = 0x00;
			break;
		default:
			printf("%s", lescan_help);
			return;
		}
	}
	helper_arg(0, 1, &argc, &argv, lescan_help);

	if (dev_id < 0)
		dev_id = hci_get_route(NULL);

	dd = hci_open_dev(dev_id);
	if (dd < 0) {
		perror("Could not open device");
		exit(1);
	}

	err = hci_le_set_scan_parameters(dd, scan_type, interval, window,
						own_type, filter_policy, 10000);
	if (err < 0) {
		perror("Set scan parameters failed");
		exit(1);
	}

	err = hci_le_set_scan_enable(dd, 0x01, filter_dup, 10000);
	if (err < 0) {
		perror("Enable scan failed");
		exit(1);
	}

	printf("LE Scan ...\n");

	err = print_advertising_devices(dd, filter_type);
	if (err < 0) {
		perror("Could not receive advertising events");
		exit(1);
	}

	err = hci_le_set_scan_enable(dd, 0x00, filter_dup, 10000);
	if (err < 0) {
		perror("Disable scan failed");
		exit(1);
	}

	hci_close_dev(dd);
}

hci_get_route取得当前device

hci_open_dev打开本机的蓝牙设备

hci_le_set_scan_parameters设置扫描参数

hci_le_set_scan_enable开始扫描

print_advertising_devices打印扫描结果

连接

参考btgatt-client工具源码:/tool/btgatt-client.c

连接使用的l2cap层,本质还是socket,网上给的例子我连接提示host is down,跟下面的源码比对后发现是安全参数设置的问题:

setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &btsec,sizeof(btsec));

这个参数设置完毕后就可以正常连接了,注意每次连接完毕后接的关闭,不然ble从设备端会拒绝下次连接。

static int l2cap_le_att_connect(bdaddr_t *src, bdaddr_t *dst, uint8_t dst_type, int sec)
{
	int sock;
	struct sockaddr_l2 srcaddr, dstaddr;
	struct bt_security btsec;
#if 1
	char srcaddr_str[18];
	char dstaddr_str[18];
	

		ba2str(src, srcaddr_str);
		ba2str(dst, dstaddr_str);

		printf("btgatt-client: Opening L2CAP LE connection on ATT "
					"channel:\n\t src: %s\n\tdest: %s\n",
					srcaddr_str, dstaddr_str);

#endif
	sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
	if (sock < 0) {
		perror("Failed to create L2CAP socket");
		return -1;
	}

	/* Set up source address */
	memset(&srcaddr, 0, sizeof(srcaddr));
	srcaddr.l2_family = AF_BLUETOOTH;
	srcaddr.l2_cid = htobs(ATT_CID);
	srcaddr.l2_bdaddr_type = 0;
	bacpy(&srcaddr.l2_bdaddr, src);

	if (bind(sock, (struct sockaddr *)&srcaddr, sizeof(srcaddr)) < 0) {
		perror("Failed to bind L2CAP socket");
		close(sock);
		return -1;
	}

	/* Set the security level */
	memset(&btsec, 0, sizeof(btsec));
	btsec.level = sec;
	if (setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &btsec,
							sizeof(btsec)) != 0) {
		printf(stderr, "Failed to set L2CAP security level\n");
		close(sock);
		return -1;
	}

	/* Set up destination address */
	memset(&dstaddr, 0, sizeof(dstaddr));
	dstaddr.l2_family = AF_BLUETOOTH;
	dstaddr.l2_cid = htobs(ATT_CID);
	dstaddr.l2_bdaddr_type = dst_type;
	bacpy(&dstaddr.l2_bdaddr, dst);

	printf("Connecting to device...");
	fflush(stdout);
	//printf("-%02x-%02x-%02x-%02x-%02x-%02x-\n",dstaddr.l2_bdaddr.b[0],dstaddr.l2_bdaddr.b[1],dstaddr.l2_bdaddr.b[2],dstaddr.l2_bdaddr.b[3],dstaddr.l2_bdaddr.b[4],dstaddr.l2_bdaddr.b[5]);
	if (connect(sock, (struct sockaddr *) &dstaddr, sizeof(dstaddr)) < 0) {
		perror(" Failed to connect");
		close(sock);
		return -1;
	}

	printf(" Done\n");

	return sock;
}

服务、特征值读写

参考btgatt-client工具源码:/tool/btgatt-client.c

源码先注册一个loop循环,然后注册一系列回调,通过回调接收发现的服务和特征值,写在源码中也有,这里就不赘述了。

static struct client *client_create(int fd, uint16_t mtu)
{
	struct client *cli;

	cli = new0(struct client, 1);
	if (!cli) {
		printf("Failed to allocate memory for client\n");
		return NULL;
	}


	cli->att = bt_att_new(fd, false);
	if (!cli->att) {
		printf(stderr, "Failed to initialze ATT transport layer\n");
		bt_att_unref(cli->att);
		free(cli);
		return NULL;
	}

	if (!bt_att_set_close_on_unref(cli->att, true)) {
		printf("Failed to set up ATT transport layer\n");
		bt_att_unref(cli->att);
		free(cli);
		return NULL;
	}

	if (!bt_att_register_disconnect(cli->att, att_disconnect_cb, NULL,
								NULL)) {
		printf(stderr, "Failed to set ATT disconnect handler\n");
		bt_att_unref(cli->att);
		free(cli);
		return NULL;
	}

	cli->fd = fd;
	cli->db = gatt_db_new();
	if (!cli->db) {
		printf("Failed to create GATT database\n");
		bt_att_unref(cli->att);
		free(cli);
		return NULL;
	}

	cli->gatt = bt_gatt_client_new(cli->db, cli->att, mtu);
	if (!cli->gatt) {
		printf("Failed to create GATT client\n");
		gatt_db_unref(cli->db);
		bt_att_unref(cli->att);
		free(cli);
		return NULL;
	}

	gatt_db_register(cli->db, service_added_cb, service_removed_cb,NULL, NULL);

	bt_att_set_debug(cli->att, att_debug_cb, "att: ", NULL);
		bt_gatt_client_set_debug(cli->gatt, gatt_debug_cb, "gatt: ",
									NULL);
	bt_gatt_client_ready_register(cli->gatt, ready_cb, cli, NULL);
	bt_gatt_client_set_service_changed(cli->gatt, service_changed_cb, cli,NULL);

	/* bt_gatt_client already holds a reference */
	gatt_db_unref(cli->db);

	return cli;
}

实现的方法有了,接下来就是如何把这些api(btgatt-client.c中用到api)为我所用了,下面是我目录结构:

文件目录介绍
btio——–头文件
include—–bluez交叉编译时生成的头文件
lib———静态依赖库,可自行修改Makefile编译
libb——–bluez交叉编译时生成的lib库
monitor—–头文件
profiles—-头文件
src———头文件
src/shared–静态依赖库,可自行修改Makefile编译
conn.c——测试demon

lib、src/shared中的代码编译成库文件,配合交叉编译bluez生成的so库一起使用即可直接调用上面提到的api了。

ps:以上基于bluez5.50源码修改,上面用到的库根据平台不同需要自行重新编译哦

我已经编译上传https://download.csdn.net/download/u010659887/10884710

https://github.com/guanggaungniao/bluez5.5_gattlib_luogf