【Linux】【蓝牙】如何基于Linux实现Beacon与Beacon Scan

7 篇文章 0 订阅
3 篇文章 0 订阅

一、背景

最近在思考如何用最精简的方式实现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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tim-Cheng

你的鼓励是我最大的动力,奥利给

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值