Beacon技术和Eddystone
Beacon是一种基于BLE的技术,以固定间隔广播发送固定格式的数据。信号强弱以RSSI相对强度来表示,遇到障碍物信号会很弱。
Beacon信标本质是一种蓝牙广播标准,该标准不是蓝牙技术联盟所制定的标准,它被称为“虚拟标准”,是由大型供应商或企业集团为首,针对广泛的Beacon应用所正式提出的蓝牙应用规范。
由苹果公司封装形成的标准是iBeacon,而Eddystone是谷歌公司制定的标准。Eddystone相对于iBeacon功能更为广泛,可以广播自定义唯一的信标ID(UID)、广播网址(URL)、广播自身数据(TLM/ETLM)、加密的临时标识符(EID)。
BLE广播包格式
广播包
如下图所示为广播包的格式,它最终会被封装在LL层packet中的PDU中
- 前导
前导是一个8bit的交替序列,根据接入地址的第一个bit为0或者1,分为01010101和10101010两种,接收机可以根据前导的无线信号强度来配置自动增益控制。 - 接入地址
接入地址有两种类型:广播接入地址和数据接入地址。
- 广播接入地址:固定为0x8E89BED6,在广播、扫描、发起连接时使用
- 数据接入地址:随机值,不同的连接有不同的值,在连接建立后两个设备间使用。
- 报头
广播报文报头格式如下:
- 报文类型(PDU Type)
- 发送地址类型(TxAdd):如果广播设备的地址是随机的,该位置1,如果是公共的,该位置0。
- 接收地址类型(RxAdd):如果目标设备的地址是随机的,该位置1,如果是公共的,该位置0。
- 长度
广播报文:长度域包含6个bit,有效范围是6~37。
数据报文:长度域包含5bit,有效值范围是0~31。
广播报文和数据报文的长度域有所不同,主要原因是:广播报文除了最多31byte的数据外,还必须要包含6byte的广播设备地址,所以需要6bit的长度域。 - 数据
广播报文中的数据格式如下:
- 有效载荷(PDU Payload)
广播和扫描响应的数据格式如下图所示,由有效数据部分和无效数据部分组成:
有效数据部分包含N个AD Structure,每个AD Structure由Length,AD Type和AD Data组成,其中:length包含AD Type和AD Data的长度,AD Type指示AD Data数据的含义。
Eddystone数据结构
数据包格式如下:
- UID
这是由三个字段组成的主要Eddystone框架,即命名空间标识符(10byte),实例标识符(6byte)和功率校准(1byte)。他将有助于将字段直接配置到BLE信标中,实例标识符旨在唯一地表示一个信标,因为他们具有不同的实例ID,功率校准字段用于根据RSSI帮助计算移动设备和Eddystone信标之间的距离。 - URL
该框架的主要目的是提供一种传输URL的方法,以便蓝牙低功耗扫描仪设备检测和发现它,然后BLE设备将连接收到并为用户显示正确的网页。BLE信标发送Eddystone-URL资源,智能手机等移动设备在检测到信标数据后立即在浏览器中自动打开网址。同时该框架提供类似于Eddystone-UID的功率校准功能。 - TLM
Eddystone-TML 框架的主要是提供关于Eddystone的健康状况的完整报告。当前温度, 当前电池电量, 正常运行时间 (信标已通电的秒数), 和 PduCount (信标在最后一次供电后部署的广告数据包的数量). - EID
这是负责小工具安全和隐私的框架。
代码验证
本例程再用ESP32S3芯片,ESP-IDF-V5.0版本做功能验证。两块ESP32S3电路板,一个作为发送方,一个作为接收方。接收方直接使用esp-idf-v5.0\examples\bluetooth\bluedroid\ble\ble_eddystone的例程,编译后直接烧录到板子中,发送方要在esp-idf-v5.0\examples\bluetooth\bluedroid\ble\ble_eddystone例程中做一些修改,让我们可以将Eddystone广播数据发送出去。
- 配置uuid,广播参数,广播数据
static uint8_t adv_service_uuid128[16] = {
/* LSB <--------------------------------------------------------------------------------> MSB */
//first uuid, 16bit, [12],[13] is the value,注意该值要改为Google的UUID,即0xFEAA
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, //slave connection min interval, Time = min_interval * 1.25 msec
.max_interval = 0x000C, //slave connection max interval, Time = max_interval * 1.25 msec
.appearance = 0x00,
.manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
.p_manufacturer_data = NULL, //&test_manufacturer[0],
.service_data_len = 0,
.p_service_data = NULL,
.service_uuid_len = 16,
.p_service_uuid = adv_service_uuid128,
.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,
};
- 封装Eddystone服务数据
//封装一个发送eddystone-UID服务的函数
int eddystone_set_uid(esp_eddystone_frame_t *uid, char 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->type = 0x16;
uid->uuid = EDDYSTONE_SERVICE_UUID;
uid->frame_type = EDDYSTONE_FRAME_TYPE_UID;
//注意SDK中esp_eddystone_frame_t中的定义u[0]改成u
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);
esp_bt_controller_enable(ESP_BT_MODE_BLE);
esp_eddystone_init();
/*<! set scan parameters */
//esp_ble_gap_set_scan_params(&ble_scan_params);
esp_eddystone_frame_t eddystone_frame;
adv_data.manufacturer_len = eddystone_set_uid(&eddystone_frame, 0, (uint8_t *)"helloworld", (uint8_t *)"654321");
adv_data.p_manufacturer_data = (uint8_t *)&eddystone_frame;
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 void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param)
{
esp_err_t err;
switch(event)
{
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
uint32_t duration = 0;
esp_ble_gap_start_scanning(duration);
break;
}
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: {
if((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(DEMO_TAG,"Scan start failed: %s", esp_err_to_name(err));
}
else {
ESP_LOGI(DEMO_TAG,"Start scanning...");
}
break;
}
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
esp_ble_gap_cb_param_t* scan_result = (esp_ble_gap_cb_param_t*)param;
switch(scan_result->scan_rst.search_evt)
{
case ESP_GAP_SEARCH_INQ_RES_EVT: {
esp_eddystone_result_t eddystone_res;
memset(&eddystone_res, 0, sizeof(eddystone_res));
esp_err_t ret = esp_eddystone_decode(scan_result->scan_rst.ble_adv, scan_result->scan_rst.adv_data_len, &eddystone_res);
if (ret) {
// error:The received data is not an eddystone frame packet or a correct eddystone frame packet.
// just return
return;
} else {
// The received adv data is a correct eddystone frame packet.
// Here, we get the eddystone infomation in eddystone_res, we can use the data in res to do other things.
// For example, just print them:
ESP_LOGI(DEMO_TAG, "--------Eddystone Found----------");
esp_log_buffer_hex("EDDYSTONE_DEMO: Device address:", scan_result->scan_rst.bda, ESP_BD_ADDR_LEN);
ESP_LOGI(DEMO_TAG, "RSSI of packet:%d dbm", scan_result->scan_rst.rssi);
esp_eddystone_show_inform(&eddystone_res);
}
break;
}
default:
break;
}
break;
}
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:{
if((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(DEMO_TAG,"Scan stop failed: %s", esp_err_to_name(err));
}
else {
ESP_LOGI(DEMO_TAG,"Stop scan successfully");
}
break;
}
//广播数据设置完成后开始广播数据
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: {
ESP_LOGI(DEMO_TAG,"start advertising .........");
esp_ble_gap_start_advertising(&adv_params);
break;
}
default:
break;
}
}
- 最终执行效果