linux下BLE(低功耗蓝牙协议)C语言开发笔记(2)---ble蓝牙扫描-连接-读写
前言bluez编译完后会生成很多命令行工具,比如gatttool、hcitool、bluetoothctl等,bluetoothctl的生成需要在configure的时候把--disable-test去掉。这些工具可以用来在linux环境下与ble设备进行调试,但是本人需要的是可用的c语言api,如果你只是开发经典蓝牙,那么恭喜你,交叉编译完后的的api足够用了;但是低功耗蓝牙用的C接口是没有..
前言
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。
这件事已经有老外实现了,但是我在交叉编译过程中遇到很多问题未解决,所以并未再继续,有兴趣的可以看看:
扫描
扫描参考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源码修改,上面用到的库根据平台不同需要自行重新编译哦
github:https://github.com/guanggaungniao/bluez5.5_gattlib_luogf
更多推荐
所有评论(0)