Beacon蓝牙信标简介
Beacon蓝牙信标是一种基于蓝牙低功耗(BLE)技术的设备,主要用于提供位置信息和数据传输服务。它通过周期性地广播信号,能够在一定范围内与其他蓝牙设备进行通信,从而提供精准的位置信息和相关服务。
工作原理:Beacon蓝牙信标作为蓝牙低功耗协议中的外围设备,持续向周围广播包含设备标识的特定数据包,但不能和中心设备建立连接。这些数据包通常包含UUID、广播名称、MAC地址、major、minor、电量信息等。简而言之,不需要建立蓝牙连接便可广播数据,如果设备想要接受数据,就必须对特特定的地址进行监听。
Beacon蓝牙信标广播标准下又分为了若干种广播格式,此次学习便以其中的一种Eddystone格式为例子(ESP32例程中有相关的,便于学习)。
Eddystone广播格式简介
Eddystone广播有四种帧格式,
可以根据配置,广播四种不同类型的数据,非常强大。
Eddystone广播包是装载于BLE通用广播包,PDU中的Playload。
引用一张网图如下(这个一整大块就是PDU中的一个Playload)。
第一个AD structure是有关于物理连接功能的配置。
第二个AD structure是有关UUID的配置。
第三个AD structure是有关服务数据的配置,这里的数据包含了UUID和Eddystone的数据。既len-1(类型占用的一个字节)=UUID+Eddystone Data;
代码实现
这部分网上有一些其他作者已经实现,就不再赘述,着重讲一下效果部分。以及我个人的一些理解分析和疑问。
主函数部分:
void app_main(void)
{//初始化ESP_ERROR_CHECK(nvs_flash_init());//释放经典蓝牙ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));//初始化并开启低功耗蓝牙控制器esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();esp_bt_controller_init(&bt_cfg);//开启低功耗BLE控制器esp_bt_controller_enable(ESP_BT_MODE_BLE);//开启eddystone初始化esp_eddystone_init();/*<! set scan parameters *///设置扫描参数//esp_ble_gap_set_scan_params(&ble_scan_params);//服务数据封包esp_eddystone_frame_t uid = {0};int len = Eddystone_set_uid(&uid,0,(uint8_t *)"helloworld",(uint8_t *)"123456");if(len==-1){printf("erro\r\n");return;}adv_data.manufacturer_len = len;adv_data.p_manufacturer_data = (uint8_t *)&uid;//设置广播数据参数esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);if (ret){ESP_LOGE(DEMO_TAG, "config adv data failed, error code = %x", ret);}//开始广播}
数据封装:
int Eddystone_set_uid(esp_eddystone_frame_t *uid,uint8_t power,uint8_t *name_space,uint8_t *instance)
{if(uid==NULL||name_space==NULL||instance==NULL){return -1;}memset(uid,0,sizeof(esp_eddystone_frame_t));uid->len = sizeof(esp_eddystone_frame_t);uid->uuid = EDDYSTONE_SERVICE_UUID;uid->frame_type = EDDYSTONE_FRAME_TYPE_UID;uid->type = 0x16;uid->u.uid.ranging_data = power;memcpy(uid->u.uid.namespace_id,name_space,10);memcpy(uid->u.uid.instance_id,instance,6);return uid->len;
}
回调函数:
void app_main(void)
{//初始化ESP_ERROR_CHECK(nvs_flash_init());//释放经典蓝牙ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));//初始化并开启低功耗蓝牙控制器esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();esp_bt_controller_init(&bt_cfg);//开启低功耗BLE控制器esp_bt_controller_enable(ESP_BT_MODE_BLE);//开启eddystone初始化esp_eddystone_init();/*<! set scan parameters *///设置扫描参数//esp_ble_gap_set_scan_params(&ble_scan_params);//服务数据封包esp_eddystone_frame_t uid = {0};int len = Eddystone_set_uid(&uid,0,(uint8_t *)"helloworld",(uint8_t *)"123456");if(len==-1){printf("erro\r\n");return;}adv_data.manufacturer_len = len;adv_data.p_manufacturer_data = (uint8_t *)&uid;//设置广播数据参数esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);if (ret){ESP_LOGE(DEMO_TAG, "config adv data failed, error code = %x", ret);}//开始广播}
参数配置:
static uint8_t adv_service_uuid[]={0xfb,0x34,0x9b,0x5f,0x80,0x00,0x00,0x80,0x00,0x10,0x00,0x00,0xaa,0xfe,0x00,0x00
};
//广播数据
static esp_ble_adv_data_t adv_data={.set_scan_rsp = false,.include_name = false,.include_txpower =false,.min_interval = 0x0006,.max_interval = 0x000c,.appearance = 0x00,.manufacturer_len = 0,.p_manufacturer_data = NULL,.service_data_len = 0,.p_service_data = NULL,.service_uuid_len = 16,.p_service_uuid = adv_service_uuid,.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
//广播参数
static esp_ble_adv_params_t adv_params = {.adv_int_min = 0x20,.adv_int_max = 0x40,.adv_type = ADV_TYPE_IND,.own_addr_type = BLE_ADDR_TYPE_PUBLIC,//.peer_addr =//.peer_addr_type =.channel_map = ADV_CHNL_ALL,.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
效果展示
分析
以上图手机抓包的数据为例,第一包数据就是我们配置的物理连接功能基础参数。
对应的参数支持如下
着重讲一下这包数据,可以发现在抓包的数据中,并没有体现这一部分的数据。一开始我也很奇怪,后来经过细致的了解后才明白,这里的配置决定了设备在广告或后续连接过程中能够声明和提供的服务。注意,是能够声明和提供,并不是已经声明和提供,这部分的配置意味着,将BLE蓝牙配置为有哪些功能,至于是否把功能开放广播出来,又是另外一回事。所以如果没开放广播,在抓包的时候自然看不见这部分信息。
最后是
这一包数据,可能有人会好奇为什么是封包在这个manufacturer而不是service这里,我一开始也有疑问,细细了解以后发表一下我的理解。首先eddystone并非是官方的蓝牙协议,只是厂商制定的一种蓝牙广播协议标准,所以它包含的应该是厂家的特定信息,而这里的manufacture data的意为,制造商数据允许设备在广告中包含自定义信息,而这些信息对特定制造商有意义。就如eddystone,是谷歌制造的协议,属于厂家自定义的信息,而并不属于通用的。所以应该放在此处。而从我们蓝牙抓包的第二条数据也可以看到。类型是0xff。
也意为制造商特定数据。
因此eddystone的协议包放在类型为0xff的AD structure中也便好理解了。至于包中的内容,与eddystone的广播格式对应即可。
疑问
最后,查阅eddystone的协议规范发现,标准的eddystone协议包应该包含一位完整的16位的uuid既,len = 0x03 type = 0x03 UUID = 0xFEAA这样一包数据,但可以看到esp32发出的数据包并不包含,确不影响到设备的扫描,但同时在nRF Connect中也并没有把他识别为一个标准的eddystone设备,而是一个普通蓝牙设备,不过不影响数据的收发。协议中对于这一包数据的解析翻译如下:该列表必须包含Eddystone服务UUID(0xFEAA),以便iOS设备进行后台扫描。个人推测即便不加也不影响基础的广播功能。同时如果各位有啥办法能够加上,也不妨可以说一声完善一下(我是试了好多方法都没加上去)。