Merge branch 'feature/btdm_eddystone' into 'master'

component/bt: Add eddystone demo

See merge request !1076
pull/1128/head
Jiang Jiang Jian 2017-10-12 17:56:05 +08:00
commit 276034354e
8 zmienionych plików z 600 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,11 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := ble_eddystone_demo
COMPONENT_ADD_INCLUDEDIRS := components/include
include $(IDF_PATH)/make/project.mk

Wyświetl plik

@ -0,0 +1,22 @@
ESP-IDF Eddystone demo
========================
This example demonstrates Eddystone-compatible BLE scanning of eddystone frame,including UID,URL.
Eddystone is an open beacon protocol specification from Google aimed at improving “proximity-based experiences”
with support for both Android and iOS smart device platforms.
Learn more on https://developers.google.com/beacons and https://github.com/google/eddystone.
esp_eddystone_protocol.h
==========================
This header file includes some eddystone-protocol related definitions.
esp_eddystone_api.h & esp_eddystone_api.c
===========================================
These files contains the decoding and decoded result of an eddystone frame packet.
You just need to include esp_eddystone_protocol.h, esp_eddystone_api.h and esp_eddystone_api.c for development.
esp_eddystone_demo.c
======================
This is an example/demo of using esp_eddystone_protocol.h, esp_eddystone_api.h and esp_eddystone_api.c files to resolve eddystone packet.

Wyświetl plik

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

Wyświetl plik

@ -0,0 +1,203 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/****************************************************************************
*
* This file is used to decode eddystone information.
*
****************************************************************************/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include "esp_err.h"
#include "esp_gap_ble_api.h"
#include "esp_eddystone_protocol.h"
#include "esp_eddystone_api.h"
/* 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);
/* Eddystone-URL scheme prefixes */
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"
};
/* decode and store received UID */
static esp_err_t esp_eddystone_uid_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
{
uint8_t pos = 0;
if(len+4 != EDDYSTONE_UID_FRAME_LEN) {
//ERROR:uid len wrong
return -1;
}
res->inform.uid.ranging_data = buf[pos++];
for(int i=0; i<10; i++) {
res->inform.uid.namespace_id[i] = buf[pos++];
}
for(int i=0; i<6; i++) {
res->inform.uid.instance_id[i] = buf[pos++];
}
return 0;
}
/* resolve received URL to url_res pointer */
static char* esp_eddystone_resolve_url_scheme(const uint8_t *url_start, const uint8_t *url_end)
{
int pos = 0;
static char url_buf[100] = {0};
const uint8_t *p = url_start;
pos += sprintf(&url_buf[pos], "%s", eddystone_url_prefix[*p++]);
for (; p <= url_end; p++) {
if (esp_eddystone_is_char_invalid((*p))) {
pos += sprintf(&url_buf[pos], "%s", eddystone_url_encoding[*p]);
} else {
pos += sprintf(&url_buf[pos], "%c", *p);
}
}
return url_buf;
}
/* decode and store received URL, the pointer url_res points to the resolved url */
static esp_err_t esp_eddystone_url_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
{
char *url_res = NULL;
uint8_t pos = 0;
if(len-1 > EDDYSTONE_URL_MAX_LEN) {
//ERROR:too long url
return -1;
}
res->inform.url.tx_power = buf[pos++];
url_res = esp_eddystone_resolve_url_scheme(buf+pos, buf+len-1);
memcpy(&res->inform.url.url, url_res, strlen(url_res));
return 0;
}
/* decode and store received TLM */
static esp_err_t esp_eddystone_tlm_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
{
uint8_t pos = 0;
if(len+4 > EDDYSTONE_TLM_FRAME_LEN) {
//ERROR:TLM too long
return -1;
}
res->inform.tlm.version = buf[pos++];
res->inform.tlm.battery_voltage = big_endian_read_16(buf, pos);
pos += 2;
uint16_t temp = big_endian_read_16(buf, pos);
int8_t temp_integral = (int8_t)((temp >> 8) & 0xff);
float temp_decimal = (temp & 0xff) / 256.0;
res->inform.tlm.temperature = temp_integral + temp_decimal;
pos += 2;
res->inform.tlm.adv_count = big_endian_read_32(buf, pos);
pos += 4;
res->inform.tlm.time = big_endian_read_32(buf, pos);
return 0;
}
static esp_err_t esp_eddystone_get_inform(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
{
static esp_err_t ret=-1;
switch(res->common.frame_type)
{
case EDDYSTONE_FRAME_TYPE_UID: {
ret = esp_eddystone_uid_received(buf, len, res);
break;
}
case EDDYSTONE_FRAME_TYPE_URL: {
ret = esp_eddystone_url_received(buf, len, res);
break;
}
case EDDYSTONE_FRAME_TYPE_TLM: {
ret = esp_eddystone_tlm_received(buf, len, res);
break;
}
default:
break;
}
return ret;
}
esp_err_t esp_eddystone_decode(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res)
{
static uint8_t pos=0;
while(res->common.srv_data_type != EDDYSTONE_SERVICE_UUID)
{
pos++;
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);
}

Wyświetl plik

@ -0,0 +1,76 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __ESP_EDDYSTONE_API_H__
#define __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]);
}
/*
* The esp eddystone API.
* This function is called to decode eddystone information from adv_data.
* The res points to the result struct.
*
*/
esp_err_t esp_eddystone_decode(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res);
//bool esp_eddystone_is_eddystone_packet(.....);
#endif /* __ESP_EDDYSTONE_API_H__ */

Wyświetl plik

@ -0,0 +1,177 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/****************************************************************************
*
* This file is used for eddystone receiver.
*
****************************************************************************/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "bt.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_defs.h"
#include "esp_gattc_api.h"
#include "esp_gap_ble_api.h"
#include "freertos/FreeRTOS.h"
#include "esp_eddystone_protocol.h"
#include "esp_eddystone_api.h"
static const char* DEMO_TAG = "EDDYSTONE_DEMO";
/* 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);
static esp_ble_scan_params_t ble_scan_params = {
.scan_type = BLE_SCAN_TYPE_ACTIVE,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_interval = 0x50,
.scan_window = 0x30
};
static void esp_eddystone_show_inform(const esp_eddystone_result_t* res)
{
switch(res->common.frame_type)
{
case EDDYSTONE_FRAME_TYPE_UID: {
ESP_LOGI(DEMO_TAG, "Eddystone UID inform:");
ESP_LOGI(DEMO_TAG, "Measured power(RSSI at 0m distance):%d dbm", res->inform.uid.ranging_data);
ESP_LOGI(DEMO_TAG, "EDDYSTONE_DEMO: Namespace ID:0x");
esp_log_buffer_hex(DEMO_TAG, res->inform.uid.namespace_id, 10);
ESP_LOGI(DEMO_TAG, "EDDYSTONE_DEMO: Instance ID:0x");
esp_log_buffer_hex(DEMO_TAG, res->inform.uid.instance_id, 6);
break;
}
case EDDYSTONE_FRAME_TYPE_URL: {
ESP_LOGI(DEMO_TAG, "Eddystone URL inform:");
ESP_LOGI(DEMO_TAG, "Measured power(RSSI at 0m distance):%d dbm", res->inform.url.tx_power);
ESP_LOGI(DEMO_TAG, "URL: %s", res->inform.url.url);
break;
}
case EDDYSTONE_FRAME_TYPE_TLM: {
ESP_LOGI(DEMO_TAG, "Eddystone TLM inform:");
ESP_LOGI(DEMO_TAG, "version: %d", res->inform.tlm.version);
ESP_LOGI(DEMO_TAG, "battery voltage: %d mV", res->inform.tlm.battery_voltage);
ESP_LOGI(DEMO_TAG, "beacon temperature in degrees Celsius: %6.1f", res->inform.tlm.temperature);
ESP_LOGI(DEMO_TAG, "adv pdu count since power-up: %d", res->inform.tlm.adv_count);
ESP_LOGI(DEMO_TAG, "time since power-up: %d s", res->inform.tlm.time);
break;
}
default:
break;
}
}
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param)
{
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(param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(DEMO_TAG,"Scan start failed");
}
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(param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(DEMO_TAG,"Scan stop failed");
}
else {
ESP_LOGI(DEMO_TAG,"Stop scan successfully");
}
break;
}
default:
break;
}
}
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,error code = %x",status);
return;
}
}
void esp_eddystone_init(void)
{
esp_bluedroid_init();
esp_bluedroid_enable();
esp_eddystone_appRegister();
}
void app_main()
{
ESP_ERROR_CHECK(nvs_flash_init());
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_BTDM);
esp_eddystone_init();
/*<! set scan parameters */
esp_ble_gap_set_scan_params(&ble_scan_params);
}

Wyświetl plik

@ -0,0 +1,103 @@
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef __ESP_EDDYSTONE_PROTOCOL_H__
#define __ESP_EDDYSTONE_PROTOCOL_H__
#include "stdbool.h"
#include "stdint.h"
/* 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
#define EDDYSTONE_UID_FRAME_LEN 0x17
#define EDDYSTONE_TLM_FRAME_LEN 0x11
#define EDDYSTONE_URL_MAX_LEN 18
/* 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__ */

Wyświetl plik

@ -0,0 +1,4 @@
# Override some defaults so BT stack is enabled
# and WiFi disabled by default in this example
CONFIG_BT_ENABLED=y
CONFIG_WIFI_ENABLED=n