一、背景
最近在思考如何用最精简的方式实现beacon与beacon scan
二、实现方式
1,mgmt实现逻辑
a: 扫描命令:
{ "find", "[-l|-b] [-L]", cmd_find, "Discover nearby devices" },
{ "find-service", "[-u UUID] [-r RSSI_Threshold] [-l|-b]", cmd_find_service, "Discover nearby service" },
{ "stop-find", "[-l|-b]", cmd_stop_find, "Stop discovery" },
例如: btmgmt find -l
同时:需要在初始化注册discovery的回调函数(mgmt_register(mgmt, MGMT_EV_DEVICE_FOUND, index, device_found,mgmt, NULL);),貌似很简单!!!早期我使用该方法
但是受限内核默认参数无法通过btmgmt设定scan interval/window/timeout等,
需要打开debugfs关于bluetooth的接口。总而言之,有些被动。
b:广播命令:
{ "add-adv", "[options] <instance_id>", cmd_add_adv, "Add advertising instance" },
例如: btmgmt add-adv
关闭广播命令:
{ "rm-adv", "<instance_id>", cmd_rm_adv, "Remove advertising instance" },
{ "clr-adv", NULL,cmd_clr_adv, "Clear advertising instances" },
例如:btmgmt clr-adv 0 or btmgmt rm-adv 0
同样,adv interval受限debugfs关于bluetooth的接口
2,dbus实现逻辑(非必要不推荐)
// dbus实现discovery的接口
static void cmd_scan(int argc, char *argv[])
{
dbus_bool_t enable;
const char *method;
if (!parse_argument(argc, argv, NULL, NULL, &enable, NULL))
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (check_default_ctrl() == FALSE)
return bt_shell_noninteractive_quit(EXIT_FAILURE);
if (enable == TRUE) {
set_discovery_filter(false);
method = "StartDiscovery";
} else
method = "StopDiscovery";
if (g_dbus_proxy_method_call(default_ctrl->proxy, method,
NULL, start_discovery_reply,
GUINT_TO_POINTER(enable), NULL) == FALSE) {
bt_shell_printf("Failed to %s discovery\n",
enable == TRUE ? "start" : "stop");
return bt_shell_noninteractive_quit(EXIT_FAILURE);
}
}
// 扫描后回调
static void device_added(GDBusProxy *proxy)
{
DBusMessageIter iter;
struct adapter *adapter = find_parent(proxy);
if (!adapter) {
/* TODO: Error */
return;
}
adapter->devices = g_list_append(adapter->devices, proxy);
print_device(proxy, COLORED_NEW);
bt_shell_set_env(g_dbus_proxy_get_path(proxy), proxy);
if (default_dev)
return;
if (g_dbus_proxy_get_property(proxy, "Connected", &iter)) {
dbus_bool_t connected;
dbus_message_iter_get_basic(&iter, &connected);
if (connected)
set_default_device(proxy, NULL);
}
}
// 关于Dbus实现的接口需要dbus-deamon以及bluetoothd支持,该方式并不适用于我们简单实现beacon/ble gatt等等,当然高级应用可以依赖于此接口
3、HCI实现逻辑
scan:源码可参考hcitool lescan
beacon:当前adv接口暂时没有在bluez源码中,本人直接按照hci接口实现逻辑
//a,hci实现beacon
#define XX_BLE_LOCAL_NAME "xx"
#define XX_BLE_DEFAULT_HCI_INDEX 0
#define XX_BLE_DEFAULT_ADV_MAX_INTERVAL 0x10
#define XX_BLE_DEFAULT_ADV_MIN_INTERVAL 0x10
#define XX_BLE_DEFAULT_CONN_MAX_INTERVAL 80
#define XX_BLE_DEFAULT_CONN_MIN_INTERVAL 60
#define XX_BLE_DEFAULT_LATENCY 0
#define XX_BLE_DEFAULT_SUPERVISION 0x0C80
#define XX_BLE_DEFAULT_SCAN_INTERVAL 0x12
#define XX_BLE_DEFAULT_SCAN_WINDOW 0x12
#define XX_BLE_DEFAULT_REGISTER_SCAN_CALLBACK 1
static int xx_ble_hci_scan = 0;
static int xx_ble_hci_scan_close = 0;
static int xx_ble_hci_scan_fd = 0;
typedef struct {
uint32_t len;
uint8_t *data;
}xx_ble_data_t;
int xx_ble_hci_get(void)
{
return hci_open_dev(XX_BLE_DEFAULT_HCI_INDEX);
}
void xx_ble_hci_close(int fd)
{
hci_close_dev(fd);
}
void xx_ble_hci_update_conn_para(void)
{
int dd;
uint16_t handle = 0;
evt_le_connection_update_complete evt;
le_connection_update_cp cp;
struct hci_request rq;
dd = xx_ble_hci_get();
if (dd < 0) {
fprintf(stderr, "HCI device open failed\n");
}
memset(&cp, 0, sizeof(cp));
cp.handle = handle;//24;
cp.min_interval = XX_BLE_DEFAULT_CONN_MIN_INTERVAL;
cp.max_interval = XX_BLE_DEFAULT_CONN_MAX_INTERVAL;
cp.latency = XX_BLE_DEFAULT_LATENCY;
cp.supervision_timeout = XX_BLE_DEFAULT_SUPERVISION;
cp.min_ce_length = htobs(0x0001);
cp.max_ce_length = htobs(0x0001);
memset(&rq, 0, sizeof(rq));
rq.ogf = OGF_LE_CTL;
rq.ocf = OCF_LE_CONN_UPDATE;
rq.cparam = &cp;
rq.clen = LE_CONN_UPDATE_CP_SIZE;
rq.event = EVT_LE_CONN_UPDATE_COMPLETE;
rq.rparam = &evt;
rq.rlen = sizeof(evt);
if (hci_send_req(dd, &rq, 5000) < 0) {
xx_ble_hci_close(dd);
xx_eprintf("Set Conn Para Error\n");
return;
}
xx_ble_hci_close(dd);
}
static void set_random_address(int fd)
{
le_set_random_address_cp cmd;
int urandom_fd;
memset(&cmd, 0, sizeof(cmd));
urandom_fd = open("/dev/urandom", O_RDONLY);
if (urandom_fd < 0) {
fprintf(stderr, "Failed to open /dev/urandom device\n");
cmd.bdaddr.b[5] = 0xc0;
cmd.bdaddr.b[4] = 0x55;
cmd.bdaddr.b[3] = 0x44;
cmd.bdaddr.b[2] = 0x33;
cmd.bdaddr.b[1] = 0x22;
cmd.bdaddr.b[0] = 0x11;
} else {
ssize_t len;
len = read(urandom_fd, cmd.bdaddr.b, sizeof(cmd.bdaddr.b));
if (len < 0 || len != sizeof(cmd.bdaddr.b)) {
fprintf(stderr, "Failed to read random data\n");
return;
}
cmd.bdaddr.b[5] &= 0x3f;
cmd.bdaddr.b[5] |= 0xc0;
}
if (hci_send_cmd(fd, OGF_LE_CTL, OCF_LE_SET_RANDOM_ADDRESS, sizeof(cmd),&cmd) < 0)
xx_eprintf("Send msg for set random address error\n");
}
static void set_adv_parameters(int fd)
{
le_set_advertising_parameters_cp cmd;
cmd.min_interval = cpu_to_le16(XX_BLE_DEFAULT_ADV_MIN_INTERVAL);
cmd.max_interval = cpu_to_le16(XX_BLE_DEFAULT_ADV_MAX_INTERVAL);
/* 0x00: Connectable undirected advertising
* 0x03: Non connectable undirected advertising
* */
cmd.advtype = 0x00;
/* 0: public address
* 1: random address */
cmd.own_bdaddr_type = 0x00;
cmd.direct_bdaddr_type = 0x00;
memset(cmd.direct_bdaddr.b, 0, 6);
cmd.chan_map = 0x07;
cmd.filter = 0x00;
if (hci_send_cmd(fd, OGF_LE_CTL, OCF_LE_SET_ADVERTISING_PARAMETERS,
sizeof(cmd), &cmd))
xx_eprintf("Send msg for set adv params error\n");
}
// HCI Interface, For managing the ble slave
int xx_ble_set_local_name(int hci_fd, char *name, int name_len)
{
change_local_name_cp cmd;
memset(cmd.name, 0x00, sizeof(cmd));
strncpy((char *)cmd.name, name, sizeof(cmd.name));
if (hci_send_cmd(hci_fd, OGF_HOST_CTL, OCF_CHANGE_LOCAL_NAME, CHANGE_LOCAL_NAME_CP_SIZE, &cmd))
{
xx_eprintf("Set loacl name error\n");
return -1;
}
return 0;
}
int xx_ble_set_adv_data(int hci_fd, unsigned char *adv_data, int adv_len)
{
le_set_advertising_data_cp cmd;
memset(cmd.data, 0, sizeof(cmd.data));
if(adv_len > sizeof(cmd.data))
{
xx_eprintf("Adv data is more than 31\n");
return -1;
}
cmd.length = adv_len;
memcpy(cmd.data, adv_data, adv_len);
xx_ble_dump_printf("Adv Data", adv_data, adv_len);
if (hci_send_cmd(hci_fd, OGF_LE_CTL, OCF_LE_SET_ADVERTISING_DATA, sizeof(cmd), &cmd))
{
xx_eprintf("Send cmd for set adv data error\n");
return -2;
}
set_random_address(hci_fd);
set_adv_parameters(hci_fd);
return 0;
}
int xx_ble_set_scan_response(int hci_fd, unsigned char *resp_data, int resp_len)
{
le_set_scan_response_data_cp cmd;
memset(cmd.data, 0, sizeof(cmd.data));
if(resp_len > sizeof(cmd.data))
{
xx_eprintf("Adv data is more than 31");
return -1;
}
cmd.length = resp_len;
memcpy(cmd.data, resp_data, resp_len);
xx_ble_dump_printf("Rsp Data", resp_data, resp_len);
if (hci_send_cmd(hci_fd, OGF_LE_CTL, OCF_LE_SET_SCAN_RESPONSE_DATA, sizeof(cmd), &cmd))
{
xx_eprintf("Send cmd for set scan response data error");
return -2;
}
return 0;
}
int xx_ble_enable_adv(int hci_fd, int enable)
{
int state = 0x00;
if (enable)
{
state = 0x01;
}
if (hci_send_cmd(hci_fd, OGF_LE_CTL, OCF_LE_SET_ADVERTISE_ENABLE, 1, &state) < 0)
{
xx_eprintf("set advertise enable error");
return -1;
}
return 0;
}
int xx_ble_hci_power(int enable)
{
int xx_hci_fd = 0;
xx_hci_fd = xx_ble_hci_get();
if (xx_hci_fd < 0) {
xx_eprintf("Failed to open hci dev\n");
return -1;
}
if(enable) {
/* Start HCI device */
if (ioctl(xx_hci_fd, HCIDEVUP, 0) < 0) {
if (errno == EALREADY) // has been powered up
return 0;
fprintf(stderr, "Can't init device hci%d: %s (%d)\n", 0, strerror(errno), errno);
return -2;
}
}else {
/* Stop HCI device */
if (ioctl(xx_hci_fd, HCIDEVDOWN, 0) < 0) {
fprintf(stderr, "Can't down device hci%d: %s (%d)\n",0, strerror(errno), errno);
}
}
xx_ble_hci_close(xx_hci_fd);
return 0;
}
int xx_ble_hci_set_adv_rsp(xx_ble_data_t *adv, xx_ble_data_t *rsp)
{
int xx_hci_fd = 0;
int state = 0x01;
xx_hci_fd = xx_ble_hci_get();
if (xx_hci_fd < 0) {
xx_eprintf("Failed to open hci dev\n");
return -1;
}
if (xx_ble_set_local_name(xx_hci_fd, XX_BLE_LOCAL_NAME, 3)< 0) {
xx_eprintf("set local name error\n");
goto done;
}
if (xx_ble_set_adv_data(xx_hci_fd, adv->data, adv->len) < 0) {
xx_eprintf("set advertise data error\n");
goto done;
}
if (xx_ble_set_scan_response(xx_hci_fd, rsp->data, rsp->len) < 0) {
xx_eprintf("set advertise rsp error\n");
goto done;
}
if (hci_send_cmd(xx_hci_fd, OGF_LE_CTL, OCF_LE_SET_ADVERTISE_ENABLE, 1, &state) < 0)
{
xx_eprintf("set advertise enable error\n");
goto done;
}
done:
xx_ble_hci_close(xx_hci_fd);
return 0;
}
void xx_ble_hci_stop_adv(void)
{
int xx_hci_fd = 0;
int state = 0x00;
xx_hci_fd = xx_ble_hci_get();
if (xx_hci_fd < 0) {
xx_eprintf("Failed to open hci dev\n");
return;
}
if (hci_send_cmd(xx_hci_fd, OGF_LE_CTL, OCF_LE_SET_ADVERTISE_ENABLE, 1, &state) < 0)
{
xx_eprintf("set advertise enable error\n");
}
xx_ble_hci_close(xx_hci_fd);
}
//b,hci实现scan
// HCI Scan Operations
void* xx_ble_hci_scan_callback(void *arg)
{
int err;
uint8_t own_type = LE_PUBLIC_ADDRESS;
uint8_t scan_type = 0x01;
uint8_t filter_policy = 0x00;
uint16_t interval = htobs(XX_BLE_DEFAULT_SCAN_INTERVAL);
uint16_t window = htobs(XX_BLE_DEFAULT_SCAN_WINDOW);
uint8_t filter_dup = 0x01;
int xx_hci_fd = 0;
unsigned char buf[HCI_MAX_EVENT_SIZE], *ptr;
struct hci_filter nf, of;
socklen_t olen;
int len;
int8_t scan_rssi;
xx_ble_hci_scan_fd = xx_hci_fd = xx_ble_hci_get();
if (xx_hci_fd < 0) {
xx_eprintf("Failed to open hci dev\n");
return NULL;
}
err = hci_le_set_scan_parameters(xx_hci_fd, scan_type, interval, window, own_type, filter_policy, 2000);
if (err < 0) {
xx_eprintf("Set scan parameters failed, errno = %d\n",errno);
goto scan_fail;
}
err = hci_le_set_scan_enable(xx_hci_fd, 0x01, filter_dup, 2000);
if (err < 0) {
xx_eprintf("Enable scan failed\n");
goto scan_fail;
}
olen = sizeof(of);
if (getsockopt(xx_hci_fd, SOL_HCI, HCI_FILTER, &of, &olen) < 0) {
xx_eprintf("Could not get socket options\n");
goto done;
}
hci_filter_clear(&nf);
hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
hci_filter_set_event(EVT_LE_META_EVENT, &nf);
if (setsockopt(xx_hci_fd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) {
xx_eprintf("Could not set socket options\n");
goto done;
}
xx_ble_hci_scan_close = 1;
while (xx_ble_hci_scan_close) {
evt_le_meta_event *meta;
le_advertising_info *info;
char addr[18];
while ((len = read(xx_hci_fd, buf, sizeof(buf))) < 0) {
if (errno == EINTR ) {
len = 0;
goto done;
}
if (errno == EAGAIN || errno == EINTR)
continue;
goto done;
}
ptr = buf + (1 + HCI_EVENT_HDR_SIZE);
len -= (1 + HCI_EVENT_HDR_SIZE);
meta = (void *) ptr;
/* For Reference
/kernel/include/net/bluetooth/hci.h*/
if (meta->subevent == EVT_LE_ADVERTISING_REPORT && xx_ble_hci_scan) {
/* Ignoring multiple reports */
info = (le_advertising_info *) (meta->data + 1);
ba2str(&info->bdaddr, addr);
scan_rssi = info->data[info->length]; // Follow Kernel Operation
if(info->evt_type == LE_ADV_IND) {
xx_printf("%s: bdtype = 0x%02x scan_rssi = %d\n", addr, info->bdaddr_type,scan_rssi);
xx_ble_dump_printf("Adv Data:", info->data, info->length);
}else if(info->evt_type == LE_ADV_SCAN_RSP){
xx_cprintf("%s: bdtype = 0x%02x scan_rssi = %d\n", addr, info->bdaddr_type,scan_rssi);
xx_ble_dump_printf("Rsp Data:", info->data, info->length);
}
}
}
done:
setsockopt(xx_hci_fd, SOL_HCI, HCI_FILTER, &of, sizeof(of));
err = hci_le_set_scan_enable(xx_hci_fd, 0x00, 0x01, 2000);
if (err < 0) {
xx_eprintf("Disable scan failed\n");
}
xx_ble_hci_close(xx_hci_fd);
xx_cprintf("Finish scan operations\n");
return NULL;
scan_fail:
xx_printf("Scan Fail\n");
xx_ble_hci_close(xx_hci_fd);
return NULL;
}
int xx_ble_hci_scan_init(void)
{
pthread_attr_t attr;
pthread_t scan_tid;
int ret = 0;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
ret = pthread_create(&scan_tid, &attr, xx_ble_hci_scan_callback, NULL);
if(ret != 0) {
xx_printf("%s fail,ret = %d\n",__func__,ret);
}
pthread_attr_destroy(&attr);
return ret;
}
void xx_ble_hci_start_scan(void)
{
xx_ble_hci_scan = 1;
}
void xx_ble_hci_stop_scan(void)
{
xx_ble_hci_scan = 0;
}
对于用户,主要参考两个开放接口
xx_ble_hci_scan_init();
以及
xx_ble_hci_set_adv_rsp();
三、补充说明
关于hci扫描回来的数据定义以及rssi定义,可参考linux kernel中,刚开始一直不理解扫描出来的rssi在哪里(很抱歉bluez中le_advertising_info没有关于rssi的定义),过了一遍kernel,恍然大悟!!!
/* Advertising report event types */
#define LE_ADV_IND 0x00
#define LE_ADV_DIRECT_IND 0x01
#define LE_ADV_SCAN_IND 0x02
#define LE_ADV_NONCONN_IND 0x03
#define LE_ADV_SCAN_RSP 0x04
#define ADDR_LE_DEV_PUBLIC 0x00
#define ADDR_LE_DEV_RANDOM 0x01
#define HCI_EV_LE_ADVERTISING_REPORT 0x02
struct hci_ev_le_advertising_info {
__u8 evt_type;
__u8 bdaddr_type;
bdaddr_t bdaddr;
__u8 length;
__u8 data[0];
} __packed;
static void hci_le_adv_report_evt(struct hci_dev *hdev, struct sk_buff *skb)
{
u8 num_reports = skb->data[0];
void *ptr = &skb->data[1];
hci_dev_lock(hdev);
while (num_reports--) {
struct hci_ev_le_advertising_info *ev = ptr;
s8 rssi;
rssi = ev->data[ev->length];
process_adv_report(hdev, ev->evt_type, &ev->bdaddr,
ev->bdaddr_type, NULL, 0, rssi,
ev->data, ev->length);
ptr += sizeof(*ev) + ev->length + 1;
}
hci_dev_unlock(hdev);
}