eddystone
https://baike.baidu.com/item/Eddystone/18103061?fr=aladdin
Eddystone是谷歌的一个开放信标协议规范,旨在改善“基于邻近性的体验”,
同时支持Android和iOS智能设备平台。一个开源蓝牙信标平台。
这个例子是扫描的,弄错了,emm..
主要文件结构
- esp_eddystone_api.h 解码API定义的一些数据结构
- esp_eddystone_api.c 解码API
- esp_eddystone_protocol.h 解析eddstone协议时用到的数据结构。
- esp_eddystone_demo.c 主代码,
源码阅读n部曲
代码不多,大概阅读源码来了解整个项目过程还是可行的。阅读源码分以下几步:
1.主要文件用处
2.文件中导入的库以及主要数据结构和函数用处
3.流程
文件主要作用
- esp_eddystone_api.h 解码API定义的一些数据结构
- esp_eddystone_api.c 解码API
- esp_eddystone_protocol.h 解析eddstone协议时用到的数据结构。
- esp_eddystone_demo.c 主代码,
esp_eddystone_protocol.h
库
#include "stdbool.h" bool类型的一些宏以及操作
#include "stdint.h" int类型的一些宏以及操作
数据结构
一些eddystone协议的宏,包含UUID,协议对应URL等等。
TLE、UID这些概念还不知道啥意思。emmm.
/* Eddystone definitions */
#define EDDYSTONE_SERVICE_UUID 0xFEAA
#define EDDYSTONE_FRAME_TYPE_UID 0x00
#define EDDYSTONE_FRAME_TYPE_URL 0x10
#define EDDYSTONE_FRAME_TYPE_TLM 0x20
#define EDDYSTONE_FRAME_TYPE_EID 0x30
//UID
#define EDDYSTONE_UID_RANG_DATA_LEN 1
#define EDDYSTONE_UID_NAMESPACE_LEN 10
#define EDDYSTONE_UID_INSTANCE_LEN 6
#define EDDYSTONE_UID_RFU_LEN 2
#define EDDYSTONE_UID_DATA_LEN (EDDYSTONE_UID_RANG_DATA_LEN + EDDYSTONE_UID_NAMESPACE_LEN + EDDYSTONE_UID_INSTANCE_LEN)
//TLM
#define EDDYSTONE_TLM_VERSION_LEN 1
#define EDDYSTONE_TLM_BATTERY_VOLTAGE_LEN 2
#define EDDYSTONE_TLM_TEMPERATURE_LEN 2
#define EDDYSTONE_TLM_ADV_COUNT_LEN 4
#define EDDYSTONE_TLM_TIME_LEN 4
#define EDDYSTONE_TLM_DATA_LEN (EDDYSTONE_TLM_VERSION_LEN + EDDYSTONE_TLM_BATTERY_VOLTAGE_LEN + \
EDDYSTONE_TLM_TEMPERATURE_LEN + EDDYSTONE_TLM_ADV_COUNT_LEN + EDDYSTONE_TLM_TIME_LEN)
//URL
#define EDDYSTONE_URL_SCHEME_LEN 1
#define EDDYSTONE_URL_ENCODED_MAX_LEN 17
#define EDDYSTONE_URL_MAX_LEN (EDDYSTONE_URL_SCHEME_LEN + EDDYSTONE_URL_ENCODED_MAX_LEN)
#define EDDYSTONE_URL_TX_POWER_LEN 1
处理协议时候所需要的一些数据结构,一般这种结构都是对应到协议的格式中。attribute((packed))用于编译器优化的。
/* Eddystone UID frame */
typedef struct {
int8_t ranging_data; /*<! calibrated Tx power at 0m */
uint8_t namespace_id[10];
uint8_t instance_id[6];
uint8_t reserved[2];
} __attribute__((packed))esp_eddystone_uid_t;
/* Eddystone URL frame */
typedef struct {
int8_t tx_power; /*<! calibrated Tx power at 0m */
uint8_t url_scheme; /*<! encoded scheme prefix */
uint8_t encoded_url[0]; /*<! length 1-17 */
} __attribute__((packed))esp_eddystone_url_t;
/* Eddystone TLM frame */
typedef struct {
uint8_t version; /*<! TLM version,0x00 for now */
uint16_t batt; /*<! battery voltage, 1mV/bit */
uint16_t temp; /*<! beacon temperature */
uint32_t adv_count; /*<! adv pdu count since power-on or reboot */
uint32_t time; /*<! time sence power-on or reboot, a 0.1 second resolution counter */
} __attribute__((packed)) esp_eddystone_tlm_t;
/* AD Structure of flags */
typedef struct {
uint8_t len;
uint8_t type;
uint8_t flags;
} __attribute__((packed)) esp_eddystone_flags_t;
/* AD Structure of complete 16-bit service uuid */
typedef struct {
uint8_t len;
uint8_t type;
uint16_t uuid; /*<! complete list of 16-bit service UUIDs data type value */
} __attribute__((packed)) esp_eddystone_uuid_t;
/* AD Structure of eddystone frame*/
typedef struct {
uint8_t len; /*<! length of eddystone data */
uint8_t type; /*<! service data type,must be 0x16 */
uint16_t uuid; /*<! 16-bit eddystone uuid */
uint8_t frame_type;
union {
esp_eddystone_uid_t uid;
esp_eddystone_url_t url;
esp_eddystone_tlm_t tlm;
} u[0];
} __attribute__((packed)) esp_eddystone_frame_t;
/* eddystone packet type */
typedef struct {
esp_eddystone_flags_t flags;
esp_eddystone_uuid_t uuid;
esp_eddystone_frame_t frame;
} __attribute__((packed)) esp_eddystone_packet_t;
函数
内联代码,相当于把这段代码直接复制到调用处
/*
* URLs are written only with the graphic printable characters of the US-ASCII coded character set.
* The octets 00-20 and 7F-FF hexadecimal are not used.
* See “Excluded US-ASCII Characters” in RFC 2936.
*
*/
static inline bool esp_eddystone_is_char_invalid(int ch)
{
return (ch >= 0x00 && ch <= 0x20) || (ch >= 0x7f && ch <= 0xff);
}
#endif /* __ESP_EDDYSTONE_PROTOCOL_H__ */
esp_eddystone_api.h
数据结构
解析协议数据需要的数据结构,可以认为一个把接受到的数据解析成需要处理的数据。
typedef struct {
struct {
uint8_t flags; /*<! AD flags data */
uint16_t srv_uuid; /*<! complete list of 16-bit service uuid*/
uint16_t srv_data_type; /*<! service data type */
uint8_t frame_type; /*<! Eddystone UID, URL or TLM */
} common;
union {
struct {
/*<! Eddystone-UID */
int8_t ranging_data; /*<! calibrated Tx power at 0m */
uint8_t namespace_id[10];
uint8_t instance_id[6];
} uid;
struct {
/*<! Eddystone-URL */
int8_t tx_power; /*<! calibrated Tx power at 0m */
char url[EDDYSTONE_URL_MAX_LEN]; /*<! the decoded URL */
} url;
struct {
/*<! Eddystone-TLM */
uint8_t version; /*<! TLM version,0x00 for now */
uint16_t battery_voltage; /*<! battery voltage in mV */
float temperature; /*<! beacon temperature in degrees Celsius */
uint32_t adv_count; /*<! adv pdu count since power-up */
uint32_t time; /*<! time since power-up, a 0.1 second resolution counter */
} tlm;
} inform;
} esp_eddystone_result_t;
函数
内联函数,还像是将多次调用的代码抽出来。
/* utils */
static inline uint16_t little_endian_read_16(const uint8_t *buffer, uint8_t pos)
{
return ((uint16_t)buffer[pos]) | (((uint16_t)buffer[(pos)+1]) << 8);
}
static inline uint16_t big_endian_read_16(const uint8_t *buffer, uint8_t pos)
{
return (((uint16_t)buffer[pos]) << 8) | ((uint16_t)buffer[(pos)+1]);
}
static inline uint32_t big_endian_read_32(const uint8_t *buffer, uint8_t pos)
{
return (((uint32_t)buffer[pos]) << 24) | (((uint32_t)buffer[(pos)+1]) << 16) | (((uint32_t)buffer[(pos)+2]) << 8) | ((uint32_t)buffer[(pos)+3]);
}
对外暴漏的API.
esp_err_t esp_eddystone_decode(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
esp_eddystone_api.c
跟上边一样,基本上就是库、数据结构、函数分析下,这个有点多,就抽出来主要的
写下
库
#include <stdio.h> 标准库
#include <stdint.h> int处理,因此里面肯定处理了整数
#include <string.h> 同上
#include <stdbool.h> 同上
#include "esp_err.h" esp运行中一些错误以及错误代码
#include "esp_gap_ble_api.h" ibeacon相当于广播,就是GAP层
#include "esp_eddystone_protocol.h" 不用多说
#include "esp_eddystone_api.h" 不用多说
数据结构
解析数据所需要的一些匹配项等等。
static const char* eddystone_url_prefix[4] = {
"http://www.",
"https://www.",
"http://",
"https://"
};
/* Eddystone-URL HTTP URL encoding */
static const char* eddystone_url_encoding[14] = {
".com/",
".org/",
".edu/",
".net/",
".info/",
".biz/",
".gov/",
".com",
".org",
".edu",
".net",
".info",
".biz",
".gov"
};
函数
英语一翻译以下应该就知道了。
/* Declare static functions */
static esp_err_t esp_eddystone_uid_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
static esp_err_t esp_eddystone_url_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
static char* esp_eddystone_resolve_url_scheme(const uint8_t* url_start, const uint8_t* url_end);
static esp_err_t esp_eddystone_tlm_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
static esp_err_t esp_eddystone_get_inform(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
真正解析广播数据以获取有用数据操作的。其实就是按数据格式处理,从中加工出来
esp_err_t esp_eddystone_decode(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
{
if (len == 0 || buf == NULL || res == NULL) {
return -1;
}
uint8_t pos=0;
while(res->common.srv_data_type != EDDYSTONE_SERVICE_UUID)
{
pos++;
if(pos >= len ) {
return -1;
}
uint8_t ad_type = buf[pos++];
switch(ad_type)
{
case ESP_BLE_AD_TYPE_FLAG: {
res->common.flags = buf[pos++];
break;
}
case ESP_BLE_AD_TYPE_16SRV_CMPL: {
uint16_t uuid = little_endian_read_16(buf, pos);
if(uuid != EDDYSTONE_SERVICE_UUID) {
return -1;
}
res->common.srv_uuid = uuid;
pos += 2;
break;
}
case ESP_BLE_AD_TYPE_SERVICE_DATA: {
uint16_t type = little_endian_read_16(buf, pos);
pos += 2;
uint8_t frame_type = buf[pos++];
if(type != EDDYSTONE_SERVICE_UUID || !(frame_type == EDDYSTONE_FRAME_TYPE_UID || frame_type == EDDYSTONE_FRAME_TYPE_URL ||
frame_type == EDDYSTONE_FRAME_TYPE_TLM)) {
return -1;
}
res->common.srv_data_type = type;
res->common.frame_type = frame_type;
break;
}
default:
break;
}
}
return esp_eddystone_get_inform(buf+pos, len-pos, res);
}
esp_eddystone_demo.c
库
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "esp_bt.h"
#include "nvs_flash.h" nvs存储
#include "esp_log.h" esp_log
#include "esp_bt_defs.h" defs不知道
#include "esp_bt_main.h" bt_main这里面才有真正的
#include "esp_gatt_defs.h" GATT层defs是什么呢
#include "esp_gattc_api.h"
#include "esp_gap_ble_api.h"
#include "freertos/FreeRTOS.h" esp32使用freertos,所以因此。
#include "esp_eddystone_protocol.h"
#include "esp_eddystone_api.h"
函数
esp_gap_cb() gap事件回调
/* declare static functions */
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param);
static void esp_eddystone_show_inform(const esp_eddystone_result_t* res);
//注册回调
void esp_eddystone_appRegister(void)
{
esp_err_t status;
ESP_LOGI(DEMO_TAG,"Register callback");
/*<! register the scan callback function to the gap module */
if((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) {
ESP_LOGE(DEMO_TAG,"gap register error: %s", esp_err_to_name(status));
return;
}
}
// esp中host协议栈初始化部分,bluedroid协议栈对应整个协议栈host层面。
void esp_eddystone_init(void)
{
esp_bluedroid_init(); //初始化
esp_bluedroid_enable(); //使能
esp_eddystone_appRegister(); //注册本次需要使用的回调
}
//主函数。
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);
}
例子流程
eso32这些单片机首先肯定是main函数来初始化一些东西 app_main(),
其中依次是外设、controller层、host层。然后是设置设备状态。最后不应该是一个whle(1)吗?有这个疑问很正常,但是还可以通过其他方式来实现哦,就是中断喽,上边哪个回调函数就可以,每次一个事件来设置一个状态。
其过程主要为以下代码。
void app_main(void)
{ //外设初始化 nvs
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();
//controller层初始化使能
esp_bt_controller_init(&bt_cfg);
esp_bt_controller_enable(ESP_BT_MODE_BLE);
//host层初始化
esp_eddystone_init();
//设置为扫描状态
/*<! set scan parameters */
esp_ble_gap_set_scan_params(&ble_scan_params);
}
void esp_eddystone_init(void)
{ //host层初始化
esp_bluedroid_init();
//使能
esp_bluedroid_enable();
//注册回调函数
esp_eddystone_appRegister();
}
但是,esp32不是这样实现的,往下扒代码可知道,主线程初始化完之后就停止了,esp32又是双核的,所以应该是host层和ll层线程来完成整个整个过程的。esp32没有暴漏这两层的细节,知识猜测