如何用BlueZ MGMT接口实现Ble Slave
一,关于MGMT接口
1,为什么有MGMT接口
In short, it’s a new interface for user space Bluetooth components (like bluetoothd) to talk to the kernel and it aims to replace the existing raw HCI sockets.
关于这部分信息可以访问:http://www.bluez.org/the-management-interface/
同样,bluez的获取方式如下:
git clone https://git.buildroot.net/buildroot
git clone git://git.buildroot.net/buildroot
2,为什么要用MGMT接口
对于我们嵌入式软件开发而言,大致可以归纳为一下几点:
1,很重要的一点,占用资源少。(实测Dbus环境动态库接近2M,bin接近1M,还是strip之后的。对于flash很小的设备而言内存占用压力很大,而用MGMT加起来不到1M)
2,便于纠错,直接和kernel通信的接口,避免过多的进程间交互,导致问题出来,不需要花大量时间纠错。(也是避免了内核与用户空间没有冲突的风险)
3,剩下的优点不提了,对于我当前简单实现ble slave,单点连接设备而言,用的比较少。
// 感兴趣可以看一下,来自官方blog
Command queues and synchronization
Since the kernel is now responsible for all HCI traffic there’s no risk of conflicts between kernel and userspace.
Blocking operations
With the management interface there are simple asynchronous messages that are used to power on and off adapters: blocking problem solved.
Unnecessary HCI event processing
No raw HCI sockets means no promisc flag on the kernel side. So extra processing of these packets isn’t needed anymore.
Distributed security policy and logic
With the management interface only user interaction (PIN code/pass key requests, etc) and link key storage is handled on the user space side. User space will feed the kernel with all stored link keys, including the key types, upon adapter initialization. After that the kernel is responsible for handling link key requests.
An additional benefit with having an abstracted interface for security is that it can be used for the Security Manager Protocol (SMP) that’s part of the Bluetooth Low Energy (LE) specification. SMP has a similar user interaction model as SSP so the same messages between user space and the kernel can be reused.
As long as SMP is implemented on the kernel side there’d be a big problem with dealing with it from user space using the existing kernel interface since unlike SSP, SMP uses L2CAP and not HCI for messaging.
Lack of early-tracing capability
The management interface will offer a special type of tracing socket which can be used to get the HCI traffic of all connected adapters. This will allow a userspace process to catch all traffic to and from an adapter from the first moment that it is plugged in.
二,如何用MGMT实现ble slave。
1,大致思路以及需要熟悉的接口文件。(本来不想跟网上一样多BB的,上来写代码的,但是一想,这个“渔”对于写代码还是很重要的。)
思路如下:
[1] 如何实现开关蓝牙.
[2] 如何实现广播,包含广播包和响应包的设定,以及广播参数设定等等。
[3] 如何实现GATT连接.
[4] 如何实现服务注册,即收发数据.
[5] 如何实现状态通知,包含连接成功,断开成功,timeout,terminate等等.
[6] 如何实现更新连接参数,连接间隔设定等等。
需要指出,以上几点可归纳为如何两个文件以及kernel中node的设定等等。
[1][2] -->bluez-5.5x\tools\btmgmt.c
[3][4][5] -->bluez-5.5x\tools\btgatt-server.c
[6] -->部分可从btmgmt.c中获取,剩下部分可参考如下:
// 仅供参考:
cat /sys/kernel/debug/bluetooth/hci0/
adv_channel_map conn_latency dut_mode identity_resolving_keys quirk_strict_duplicate_filter ssp_debug_mode white_list_size
adv_max_interval conn_max_interval features idle_timeout random_address static_address
adv_min_interval conn_min_interval force_static_address inquiry_cache remote_oob supervision_timeout
auto_accept_delay dev_class hardware_error link_keys rpa_timeout use_debug_keys
blacklist device_id hci_revision long_term_keys sc_only_mode uuids
conn_info_max_age device_list hci_version manufacturer sniff_max_interval voice_setting
conn_info_min_age discov_interleaved_timeout identity quirk_simultaneous_discovery sniff_min_interval white_list
2,接口文件解释
[1] 如何实现开关蓝牙.
[2] 如何实现广播,包含广播包和响应包的设定,以及广播参数设定等等。
// node可以修改部分参数
[3] 如何实现GATT连接.
[4] 如何实现服务注册,即收发数据.
BlueZ提供了两份实例关于ble master以及ble slave。
其中ble slave对应上btgatt-server.c; ble master对应上btgatt-client.c
A.设定设备类型
B.设定GATT参数以及characteristic,同时注册回调
[5] 如何实现状态通知,包含连接成功,断开成功,timeout,terminate等等.
A,在设定状态等等之前,我们需要监听所有信息。
B,状态上报
[6] 如何实现更新连接参数,连接间隔设定等等。
关于更新连接参数以及连接间隔等操作,需要操作node,对应裸板实现时,需要把bluetooth的node引出,对其写操作
这部分不详述
三,如何用代码和MGMT接口实现ble slave。
1,在实现之前,我们需要理解使用socketpair实现双向通讯。实现线程间通信。
示例代码如下(以Btmgmt为例):
// 初始化如下:
void* mgmt_ble_init(MGMT_CBS *cbs)
{
int sockets[2];
int result;
int bufferSize = SOCKET_BUFFER_SIZE;
result = socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets);
if (-1 == result) {
printf("socketpair error!\n");
return NULL;
}
struct timeval mgmt_timeout;
memset(&mgmt_timeout, 0x0, sizeof(mgmt_timeout));
mgmt_timeout.tv_sec = 10;
mgmt_timeout.tv_usec = 0;
if(setsockopt(sockets[0], SOL_SOCKET, SO_SNDTIMEO, (void *)&mgmt_timeout, sizeof(mgmt_timeout)) == -1) {
printf("setsockopt: unable to set SO_RCVTIMEO");
goto failed2;
}
if(setsockopt(sockets[1], SOL_SOCKET, SO_SNDTIMEO, (void *)&mgmt_timeout, sizeof(mgmt_timeout)) == -1) {
printf("setsockopt: unable to set SO_RCVTIMEO");
goto failed2;
}
setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
struct mgmt_ble_com *hdl = NULL;
hdl = (struct mgmt_ble_com*)malloc(sizeof(struct mgmt_ble_com));
if (hdl == NULL) {
printf("malloc failed\n");
goto failed2;
}
hdl->msock[0] = sockets[0];
hdl->msock[1] = sockets[1];
memcpy(&hdl->cbs, cbs, sizeof(MGMT_CBS));
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthread_attr_setstacksize(&attr, 1024 * 1024);
pthread_t mgmt_ble_thread = 0;
if (pthread_create(&mgmt_ble_thread, &attr, mgmt_ble_process, hdl) != 0) {
printf("cannot create mgmt server thread!\n");
goto failed1;
}
pthread_t mgmt_rsp_thread = 0;
if (pthread_create(&mgmt_rsp_thread, &attr, mgmt_ble_rsp, hdl) != 0) {
printf("cannot create mgmt client thread!\n");
goto failed1;
}
pthread_attr_destroy(&attr);
return hdl;
failed1:
pthread_attr_destroy(&attr);
free(hdl);
failed2:
printf("closing socket(%d), socket(%d)\n", sockets[0], sockets[1]);
close(sockets[0]);
close(sockets[1]);
return NULL;
}
// 双向缓冲buffer如下:
void *mgmt_ble_rsp(void *arg)
{
unsigned char buf[BUFFER_SIZE] = {0};
struct mgmt_ble_com *phdl = arg;
int ret = -1;
fd_set rdfds;
struct timeval timeout;
prctl(PR_SET_NAME, "mgmg-handle rsp");
while(1) {
FD_ZERO(&rdfds);
FD_SET(phdl->msock[0], &rdfds);
timeout.tv_sec = 3;
timeout.tv_usec = 0;
if (phdl->m_num == 0) {
(phdl->m_num)++;
}
ret = select(phdl->msock[0] + 1, &rdfds, NULL, NULL, &timeout);
if (ret == 0) {
//printf("select timeout\n");
continue;
} else if (ret < 0) {
printf("select error, need to do something...\n");
break;
}
if (FD_ISSET(phdl->msock[0], &rdfds)) {
ret = read(phdl->msock[0], buf, BUFFER_SIZE);
if (ret < 0) {
printf("recv error...\n");
break;
} else if(ret == 0) {
printf("read zero\n");
break;
}
buf[ret] = '\0';
if (mgmt_ble_resp_data(phdl, buf, ret)) {
printf("exit mgmt thread\n");
break;
}
}
}
close(phdl->msock[0]);
close(phdl->msock[1]);
free(phdl);
return NULL;
}
2,操作蓝牙开关以及广播。
// 通过写操作,对端接收信息并处理事件
int ble_mgmt_power(int index,void *bt_hdl, int onoff)
{
struct mgmt_ble_com *hdl = bt_hdl;
if (hdl == NULL) {
printf("bt handle is null\n");
return -1;
}
char *cmd = onoff?"power on":"power off";
if (write(hdl->msock[0], cmd, strlen(cmd)) <= 0) {
printf("(%d) send failed: %d %s\n",
hdl->msock[0], errno, strerror(errno));
return -1;
}
return 0;
}
// 发送广播信息
int ble_mgmt_adv(int index,void *bt_hdl, tuya_ble_mgmt_adv_data_t *adv, tuya_ble_mgmt_rsp_data_t *rsp)
{
struct mgmt_ble_com *hdl = bt_hdl;
char cmd[256] = {0};
if (hdl == NULL) {
printf("bt handle is null\n");
return -1;
}
if((adv->data == NULL)||(adv->len == 0)||(rsp->data == NULL)||(rsp->len == 0)) {
return -2;
}else {
snprintf(cmd, sizeof(cmd), "add-adv -c -d %.*s -s %.*s %.*d", adv->len * 2,adv->str,rsp->len * 2,rsp->str,1,instance_id);
}
if (write(hdl->msock[0], cmd, strlen(cmd)) <= 0) {
printf("(%d) send failed: %d %s\n",
hdl->msock[0], errno, strerror(errno));
return -3;
}
return 0;
}
3,如何写操作。
A, 对于btgatt-server初始化部分如上述相同方式。我们直接贴上如何操作写数据
// 写数据
int ble_gatt_set_value(void *bt_hdl, unsigned char *data, int len)
{
struct mgmt_ble_com *hdl = bt_hdl;
char cmd[512] = {0};
int i, size;
int j = 0;
if (hdl == NULL) {
printf("bt handle is null\n");
return -1;
}
if(hdl->common_handle == 0) {
printf("error handle id for this notify");
return -1;
}
snprintf(cmd, sizeof(cmd), "notify 0x%04x ", hdl->common_handle);
size = strlen(cmd);
for (i = 0; i < len; i++) {
snprintf(cmd+size+j, sizeof(cmd), "%02x ", data[i]);
j=j+3;
}
if (ble_gatt_send_cmd(hdl, cmd) < 0)
return -1;
return 0;
}