diff --git a/examples/bluetooth/ble_eddystone/Makefile b/examples/bluetooth/ble_eddystone/Makefile new file mode 100644 index 000000000..e7b35bd9c --- /dev/null +++ b/examples/bluetooth/ble_eddystone/Makefile @@ -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 + diff --git a/examples/bluetooth/ble_eddystone/README.rst b/examples/bluetooth/ble_eddystone/README.rst new file mode 100644 index 000000000..a379b9cf6 --- /dev/null +++ b/examples/bluetooth/ble_eddystone/README.rst @@ -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. diff --git a/examples/bluetooth/ble_eddystone/main/component.mk b/examples/bluetooth/ble_eddystone/main/component.mk new file mode 100644 index 000000000..a98f634ea --- /dev/null +++ b/examples/bluetooth/ble_eddystone/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.c b/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.c new file mode 100644 index 000000000..8ab8a241a --- /dev/null +++ b/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.c @@ -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 +#include +#include +#include + +#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); +} + diff --git a/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.h b/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.h new file mode 100644 index 000000000..b83b1a959 --- /dev/null +++ b/examples/bluetooth/ble_eddystone/main/esp_eddystone_api.h @@ -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; /*> 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__ */ + diff --git a/examples/bluetooth/ble_eddystone/main/esp_eddystone_demo.c b/examples/bluetooth/ble_eddystone/main/esp_eddystone_demo.c new file mode 100644 index 000000000..c7719f6fa --- /dev/null +++ b/examples/bluetooth/ble_eddystone/main/esp_eddystone_demo.c @@ -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 +#include +#include + +#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"); + + /*= 0x00 && ch <= 0x20) || (ch >= 0x7f && ch <= 0xff); +} + +#endif /* __ESP_EDDYSTONE_PROTOCOL_H__ */ diff --git a/examples/bluetooth/ble_eddystone/sdkconfig.defaults b/examples/bluetooth/ble_eddystone/sdkconfig.defaults new file mode 100644 index 000000000..9d51df5ee --- /dev/null +++ b/examples/bluetooth/ble_eddystone/sdkconfig.defaults @@ -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