/* * BLEAdvertising.cpp * * This class encapsulates advertising a BLE Server. * Created on: Jun 21, 2017 * Author: kolban * * The ESP-IDF provides a framework for BLE advertising. It has determined that there are a common set * of properties that are advertised and has built a data structure that can be populated by the programmer. * This means that the programmer doesn't have to "mess with" the low level construction of a low level * BLE advertising frame. Many of the fields are determined for us while others we can set before starting * to advertise. * * Should we wish to construct our own payload, we can use the BLEAdvertisementData class and call the setters * upon it. Once it is populated, we can then associate it with the advertising and what ever the programmer * set in the data will be advertised. * */ #include "sdkconfig.h" #if defined(CONFIG_BLUEDROID_ENABLED) #include "BLEAdvertising.h" #include #include "BLEUtils.h" #include "GeneralUtils.h" #include "esp32-hal-log.h" /** * @brief Construct a default advertising object. * */ BLEAdvertising::BLEAdvertising() : m_scanRespData{} { m_advData.set_scan_rsp = false; m_advData.include_name = true; m_advData.include_txpower = true; m_advData.min_interval = 0x20; m_advData.max_interval = 0x40; m_advData.appearance = 0x00; m_advData.manufacturer_len = 0; m_advData.p_manufacturer_data = nullptr; m_advData.service_data_len = 0; m_advData.p_service_data = nullptr; m_advData.service_uuid_len = 0; m_advData.p_service_uuid = nullptr; m_advData.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT); m_advParams.adv_int_min = 0x20; m_advParams.adv_int_max = 0x40; m_advParams.adv_type = ADV_TYPE_IND; m_advParams.own_addr_type = BLE_ADDR_TYPE_PUBLIC; m_advParams.channel_map = ADV_CHNL_ALL; m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; m_advParams.peer_addr_type = BLE_ADDR_TYPE_PUBLIC; m_customAdvData = false; // No custom advertising data m_customScanResponseData = false; // No custom scan response data } // BLEAdvertising /** * @brief Add a service uuid to exposed list of services. * @param [in] serviceUUID The UUID of the service to expose. */ void BLEAdvertising::addServiceUUID(BLEUUID serviceUUID) { m_serviceUUIDs.push_back(serviceUUID); } // addServiceUUID /** * @brief Add a service uuid to exposed list of services. * @param [in] serviceUUID The string representation of the service to expose. */ void BLEAdvertising::addServiceUUID(const char* serviceUUID) { addServiceUUID(BLEUUID(serviceUUID)); } // addServiceUUID /** * @brief Set the device appearance in the advertising data. * The appearance attribute is of type 0x19. The codes for distinct appearances can be found here: * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml. * @param [in] appearance The appearance of the device in the advertising data. * @return N/A. */ void BLEAdvertising::setAppearance(uint16_t appearance) { m_advData.appearance = appearance; } // setAppearance void BLEAdvertising::setAdvertisementType(esp_ble_adv_type_t adv_type){ m_advParams.adv_type = adv_type; } // setAdvertisementType void BLEAdvertising::setAdvertisementChannelMap(esp_ble_adv_channel_t channel_map) { m_advParams.channel_map = channel_map; } // setAdvertisementChannelMap void BLEAdvertising::setMinInterval(uint16_t mininterval) { m_advParams.adv_int_min = mininterval; } // setMinInterval void BLEAdvertising::setMaxInterval(uint16_t maxinterval) { m_advParams.adv_int_max = maxinterval; } // setMaxInterval void BLEAdvertising::setMinPreferred(uint16_t mininterval) { m_advData.min_interval = mininterval; } // void BLEAdvertising::setMaxPreferred(uint16_t maxinterval) { m_advData.max_interval = maxinterval; } // void BLEAdvertising::setScanResponse(bool set) { m_scanResp = set; } /** * @brief Set the filtering for the scan filter. * @param [in] scanRequestWhitelistOnly If true, only allow scan requests from those on the white list. * @param [in] connectWhitelistOnly If true, only allow connections from those on the white list. */ void BLEAdvertising::setScanFilter(bool scanRequestWhitelistOnly, bool connectWhitelistOnly) { log_v(">> setScanFilter: scanRequestWhitelistOnly: %d, connectWhitelistOnly: %d", scanRequestWhitelistOnly, connectWhitelistOnly); if (!scanRequestWhitelistOnly && !connectWhitelistOnly) { m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY; log_v("<< setScanFilter"); return; } if (scanRequestWhitelistOnly && !connectWhitelistOnly) { m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_WLST_CON_ANY; log_v("<< setScanFilter"); return; } if (!scanRequestWhitelistOnly && connectWhitelistOnly) { m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_WLST; log_v("<< setScanFilter"); return; } if (scanRequestWhitelistOnly && connectWhitelistOnly) { m_advParams.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST; log_v("<< setScanFilter"); return; } } // setScanFilter /** * @brief Set the advertisement data that is to be published in a regular advertisement. * @param [in] advertisementData The data to be advertised. */ void BLEAdvertising::setAdvertisementData(BLEAdvertisementData& advertisementData) { log_v(">> setAdvertisementData"); esp_err_t errRc = ::esp_ble_gap_config_adv_data_raw( (uint8_t*)advertisementData.getPayload().data(), advertisementData.getPayload().length()); if (errRc != ESP_OK) { log_e("esp_ble_gap_config_adv_data_raw: %d %s", errRc, GeneralUtils::errorToString(errRc)); } m_customAdvData = true; // Set the flag that indicates we are using custom advertising data. log_v("<< setAdvertisementData"); } // setAdvertisementData /** * @brief Set the advertisement data that is to be published in a scan response. * @param [in] advertisementData The data to be advertised. */ void BLEAdvertising::setScanResponseData(BLEAdvertisementData& advertisementData) { log_v(">> setScanResponseData"); esp_err_t errRc = ::esp_ble_gap_config_scan_rsp_data_raw( (uint8_t*)advertisementData.getPayload().data(), advertisementData.getPayload().length()); if (errRc != ESP_OK) { log_e("esp_ble_gap_config_scan_rsp_data_raw: %d %s", errRc, GeneralUtils::errorToString(errRc)); } m_customScanResponseData = true; // Set the flag that indicates we are using custom scan response data. log_v("<< setScanResponseData"); } // setScanResponseData /** * @brief Start advertising. * Start advertising. * @return N/A. */ void BLEAdvertising::start() { log_v(">> start: customAdvData: %d, customScanResponseData: %d", m_customAdvData, m_customScanResponseData); // We have a vector of service UUIDs that we wish to advertise. In order to use the // ESP-IDF framework, these must be supplied in a contiguous array of their 128bit (16 byte) // representations. If we have 1 or more services to advertise then we allocate enough // storage to host them and then copy them in one at a time into the contiguous storage. int numServices = m_serviceUUIDs.size(); if (numServices > 0) { m_advData.service_uuid_len = 16 * numServices; m_advData.p_service_uuid = new uint8_t[m_advData.service_uuid_len]; uint8_t* p = m_advData.p_service_uuid; for (int i = 0; i < numServices; i++) { log_d("- advertising service: %s", m_serviceUUIDs[i].toString().c_str()); BLEUUID serviceUUID128 = m_serviceUUIDs[i].to128(); memcpy(p, serviceUUID128.getNative()->uuid.uuid128, 16); p += 16; } } else { m_advData.service_uuid_len = 0; log_d("- no services advertised"); } esp_err_t errRc; if (!m_customAdvData) { // Set the configuration for advertising. m_advData.set_scan_rsp = false; m_advData.include_name = !m_scanResp; m_advData.include_txpower = !m_scanResp; errRc = ::esp_ble_gap_config_adv_data(&m_advData); if (errRc != ESP_OK) { log_e("<< esp_ble_gap_config_adv_data: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } } if (!m_customScanResponseData && m_scanResp) { // Set the configuration for scan response. memcpy(&m_scanRespData, &m_advData, sizeof(esp_ble_adv_data_t)); // Copy the content of m_advData. m_scanRespData.set_scan_rsp = true; // Define this struct as scan response data m_scanRespData.include_name = true; // Caution: This may lead to a crash if the device name has more than 29 characters m_scanRespData.include_txpower = true; m_scanRespData.appearance = 0; // If defined the 'Appearance' attribute is already included in the advertising data m_scanRespData.flag = 0; // 'Flags' attribute should no be included in the scan response errRc = ::esp_ble_gap_config_adv_data(&m_scanRespData); if (errRc != ESP_OK) { log_e("<< esp_ble_gap_config_adv_data (Scan response): rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } } // If we had services to advertise then we previously allocated some storage for them. // Here we release that storage. if (m_advData.service_uuid_len > 0) { delete[] m_advData.p_service_uuid; m_advData.p_service_uuid = nullptr; } // Start advertising. errRc = ::esp_ble_gap_start_advertising(&m_advParams); if (errRc != ESP_OK) { log_e("<< esp_ble_gap_start_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } log_v("<< start"); } // start /** * @brief Stop advertising. * Stop advertising. * @return N/A. */ void BLEAdvertising::stop() { log_v(">> stop"); esp_err_t errRc = ::esp_ble_gap_stop_advertising(); if (errRc != ESP_OK) { log_e("esp_ble_gap_stop_advertising: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } log_v("<< stop"); } // stop /** * @brief Set BLE address. * @param [in] Bluetooth address. * @param [in] Bluetooth address type. * Set BLE address. */ void BLEAdvertising::setDeviceAddress(esp_bd_addr_t addr, esp_ble_addr_type_t type) { log_v(">> setPrivateAddress"); m_advParams.own_addr_type = type; esp_err_t errRc = esp_ble_gap_set_rand_addr((uint8_t*)addr); if (errRc != ESP_OK) { log_e("esp_ble_gap_set_rand_addr: rc=%d %s", errRc, GeneralUtils::errorToString(errRc)); return; } log_v("<< setPrivateAddress"); } // setPrivateAddress /** * @brief Add data to the payload to be advertised. * @param [in] data The data to be added to the payload. */ void BLEAdvertisementData::addData(std::string data) { if ((m_payload.length() + data.length()) > ESP_BLE_ADV_DATA_LEN_MAX) { return; } m_payload.append(data); } // addData /** * @brief Set the appearance. * @param [in] appearance The appearance code value. * * See also: * https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml */ void BLEAdvertisementData::setAppearance(uint16_t appearance) { char cdata[2]; cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_APPEARANCE; // 0x19 addData(std::string(cdata, 2) + std::string((char*) &appearance, 2)); } // setAppearance /** * @brief Set the complete services. * @param [in] uuid The single service to advertise. */ void BLEAdvertisementData::setCompleteServices(BLEUUID uuid) { char cdata[2]; switch (uuid.bitSize()) { case 16: { // [Len] [0x02] [LL] [HH] cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_16SRV_CMPL; // 0x03 addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid16, 2)); break; } case 32: { // [Len] [0x04] [LL] [LL] [HH] [HH] cdata[0] = 5; cdata[1] = ESP_BLE_AD_TYPE_32SRV_CMPL; // 0x05 addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid32, 4)); break; } case 128: { // [Len] [0x04] [0] [1] ... [15] cdata[0] = 17; cdata[1] = ESP_BLE_AD_TYPE_128SRV_CMPL; // 0x07 addData(std::string(cdata, 2) + std::string((char*) uuid.getNative()->uuid.uuid128, 16)); break; } default: return; } } // setCompleteServices /** * @brief Set the advertisement flags. * @param [in] The flags to be set in the advertisement. * * * ESP_BLE_ADV_FLAG_LIMIT_DISC * * ESP_BLE_ADV_FLAG_GEN_DISC * * ESP_BLE_ADV_FLAG_BREDR_NOT_SPT * * ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT * * ESP_BLE_ADV_FLAG_DMT_HOST_SPT * * ESP_BLE_ADV_FLAG_NON_LIMIT_DISC */ void BLEAdvertisementData::setFlags(uint8_t flag) { char cdata[3]; cdata[0] = 2; cdata[1] = ESP_BLE_AD_TYPE_FLAG; // 0x01 cdata[2] = flag; addData(std::string(cdata, 3)); } // setFlag /** * @brief Set manufacturer specific data. * @param [in] data Manufacturer data. */ void BLEAdvertisementData::setManufacturerData(std::string data) { log_d("BLEAdvertisementData", ">> setManufacturerData"); char cdata[2]; cdata[0] = data.length() + 1; cdata[1] = ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE; // 0xff addData(std::string(cdata, 2) + data); log_d("BLEAdvertisementData", "<< setManufacturerData"); } // setManufacturerData /** * @brief Set the name. * @param [in] The complete name of the device. */ void BLEAdvertisementData::setName(std::string name) { log_d("BLEAdvertisementData", ">> setName: %s", name.c_str()); char cdata[2]; cdata[0] = name.length() + 1; cdata[1] = ESP_BLE_AD_TYPE_NAME_CMPL; // 0x09 addData(std::string(cdata, 2) + name); log_d("BLEAdvertisementData", "<< setName"); } // setName /** * @brief Set the partial services. * @param [in] uuid The single service to advertise. */ void BLEAdvertisementData::setPartialServices(BLEUUID uuid) { char cdata[2]; switch (uuid.bitSize()) { case 16: { // [Len] [0x02] [LL] [HH] cdata[0] = 3; cdata[1] = ESP_BLE_AD_TYPE_16SRV_PART; // 0x02 addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid16, 2)); break; } case 32: { // [Len] [0x04] [LL] [LL] [HH] [HH] cdata[0] = 5; cdata[1] = ESP_BLE_AD_TYPE_32SRV_PART; // 0x04 addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid32, 4)); break; } case 128: { // [Len] [0x04] [0] [1] ... [15] cdata[0] = 17; cdata[1] = ESP_BLE_AD_TYPE_128SRV_PART; // 0x06 addData(std::string(cdata, 2) + std::string((char *) &uuid.getNative()->uuid.uuid128, 16)); break; } default: return; } } // setPartialServices /** * @brief Set the service data (UUID + data) * @param [in] uuid The UUID to set with the service data. Size of UUID will be used. * @param [in] data The data to be associated with the service data advert. */ void BLEAdvertisementData::setServiceData(BLEUUID uuid, std::string data) { char cdata[2]; switch (uuid.bitSize()) { case 16: { // [Len] [0x16] [UUID16] data cdata[0] = data.length() + 3; cdata[1] = ESP_BLE_AD_TYPE_SERVICE_DATA; // 0x16 addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid16, 2) + data); break; } case 32: { // [Len] [0x20] [UUID32] data cdata[0] = data.length() + 5; cdata[1] = ESP_BLE_AD_TYPE_32SERVICE_DATA; // 0x20 addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid32, 4) + data); break; } case 128: { // [Len] [0x21] [UUID128] data cdata[0] = data.length() + 17; cdata[1] = ESP_BLE_AD_TYPE_128SERVICE_DATA; // 0x21 addData(std::string(cdata, 2) + std::string((char*) &uuid.getNative()->uuid.uuid128, 16) + data); break; } default: return; } } // setServiceData /** * @brief Set the short name. * @param [in] The short name of the device. */ void BLEAdvertisementData::setShortName(std::string name) { log_d("BLEAdvertisementData", ">> setShortName: %s", name.c_str()); char cdata[2]; cdata[0] = name.length() + 1; cdata[1] = ESP_BLE_AD_TYPE_NAME_SHORT; // 0x08 addData(std::string(cdata, 2) + name); log_d("BLEAdvertisementData", "<< setShortName"); } // setShortName /** * @brief Retrieve the payload that is to be advertised. * @return The payload that is to be advertised. */ std::string BLEAdvertisementData::getPayload() { return m_payload; } // getPayload void BLEAdvertising::handleGAPEvent( esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param) { log_d("handleGAPEvent [event no: %d]", (int)event); switch(event) { case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: { // m_semaphoreSetAdv.give(); break; } case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: { // m_semaphoreSetAdv.give(); break; } case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: { // m_semaphoreSetAdv.give(); break; } case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: { log_i("STOP advertising"); //start(); break; } default: break; } } #ifdef CONFIG_BT_BLE_50_FEATURES_SUPPORTED /** * @brief Creator * * @param[in] instance : number of multi advertising instances * * */ BLEMultiAdvertising::BLEMultiAdvertising(uint8_t num) { params_arrays = (esp_ble_gap_ext_adv_params_t*)calloc(num, sizeof(esp_ble_gap_ext_adv_params_t)); ext_adv = (esp_ble_gap_ext_adv_t*)calloc(num, sizeof(esp_ble_gap_ext_adv_t)); count = num; } /** * @brief This function is used by the Host to set the advertising parameters. * * @param[in] instance : identifies the advertising set whose parameters are being configured. * @param[in] params : advertising parameters * * @return - true : success * - false : failed * */ bool BLEMultiAdvertising::setAdvertisingParams(uint8_t instance, const esp_ble_gap_ext_adv_params_t* params) { if (params->type == ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY_IND && params->primary_phy == ESP_BLE_GAP_PHY_2M) return false; esp_err_t rc; rc = esp_ble_gap_ext_adv_set_params(instance, params); return ESP_OK == rc; } /** * @brief This function is used to set the data used in advertising PDUs that have a data field * * @param[in] instance : identifies the advertising set whose data are being configured * @param[in] length : data length * @param[in] data : data information * * @return - true : success * - false : failed * */ bool BLEMultiAdvertising::setAdvertisingData(uint8_t instance, uint16_t length, const uint8_t* data) { esp_err_t rc; rc = esp_ble_gap_config_ext_adv_data_raw(instance, length, data); if (rc) log_e("set advertising data err: %d", rc); return ESP_OK == rc; } bool BLEMultiAdvertising::setScanRspData(uint8_t instance, uint16_t length, const uint8_t* data) { esp_err_t rc; rc = esp_ble_gap_config_ext_scan_rsp_data_raw(instance, length, data); if (rc) log_e("set scan resp data err: %d", rc); return ESP_OK == rc; } /** * @brief This function is used to request the Controller to enable one or more * advertising sets using the advertising sets identified by the instance parameter. * * @return - true : success * - false : failed * */ bool BLEMultiAdvertising::start() { return start(count, 0); } /** * @brief This function is used to request the Controller to enable one or more * advertising sets using the advertising sets identified by the instance parameter. * * @param[in] num : Number of advertising sets to enable or disable * @param[in] from : first sxt adv set to use * * @return - true : success * - false : failed * */ bool BLEMultiAdvertising::start(uint8_t num, uint8_t from) { if (num > count || from >= count) return false; esp_err_t rc; rc = esp_ble_gap_ext_adv_start(num, &ext_adv[from]); if (rc) log_e("start extended advertising err: %d", rc); return ESP_OK == rc; } /** * @brief This function is used to request the Controller to disable one or more * advertising sets using the advertising sets identified by the instance parameter. * * @param[in] num_adv : Number of advertising sets to enable or disable * @param[in] ext_adv_inst : ext adv instance * * @return - ESP_OK : success * - other : failed * */ bool BLEMultiAdvertising::stop(uint8_t num_adv, const uint8_t* ext_adv_inst) { esp_err_t rc; rc = esp_ble_gap_ext_adv_stop(num_adv, ext_adv_inst); if (rc) log_e("stop extended advertising err: %d", rc); return ESP_OK == rc; } /** * @brief This function is used to remove an advertising set from the Controller. * * @param[in] instance : Used to identify an advertising set * * @return - ESP_OK : success * - other : failed * */ bool BLEMultiAdvertising::remove(uint8_t instance) { esp_err_t rc; rc = esp_ble_gap_ext_adv_set_remove(instance); if (rc) log_e("remove extended advertising err: %d", rc); return ESP_OK == rc; } /** * @brief This function is used to remove all existing advertising sets from the Controller. * * * @return - ESP_OK : success * - other : failed * */ bool BLEMultiAdvertising::clear() { esp_err_t rc; rc = esp_ble_gap_ext_adv_set_clear(); if (rc) log_e("clear extended advertising err: %d", rc); return ESP_OK == rc; } /** * @brief This function is used by the Host to set the random device address specified by the Random_Address parameter. * * @param[in] instance : Used to identify an advertising set * @param[in] addr_legacy : Random Device Address * * @return - true : success * - false : failed * */ bool BLEMultiAdvertising::setInstanceAddress(uint8_t instance, uint8_t* addr_legacy) { esp_err_t rc; rc = esp_ble_gap_ext_adv_set_rand_addr(instance, addr_legacy); if (rc) log_e("set random address err: %d", rc); return ESP_OK == rc; } /** * @brief This function is used by the Host to set the parameters for periodic advertising. * * @param[in] instance : identifies the advertising set whose periodic advertising parameters are being configured. * @param[in] params : periodic adv parameters * * @return - true : success * - false : failed * */ bool BLEMultiAdvertising::setPeriodicAdvertisingParams(uint8_t instance, const esp_ble_gap_periodic_adv_params_t* params) { esp_err_t rc; rc = esp_ble_gap_periodic_adv_set_params(instance, params); if (rc) log_e("set periodic advertising params err: %d", rc); return ESP_OK == rc; } /** * @brief This function is used to set the data used in periodic advertising PDUs. * * @param[in] instance : identifies the advertising set whose periodic advertising parameters are being configured. * @param[in] length : the length of periodic data * @param[in] data : periodic data information * * @return - true : success * - false : failed * */ bool BLEMultiAdvertising::setPeriodicAdvertisingData(uint8_t instance, uint16_t length, const uint8_t* data) { esp_err_t rc; rc = esp_ble_gap_config_periodic_adv_data_raw(instance, length, data); if (rc) log_e("set periodic advertising raw data err: %d", rc); return ESP_OK == rc; } /** * @brief This function is used to request the Controller to enable the periodic advertising for the advertising set specified * * @param[in] instance : Used to identify an advertising set * * @return - true : success * - false : failed * */ bool BLEMultiAdvertising::startPeriodicAdvertising(uint8_t instance) { esp_err_t rc; rc = esp_ble_gap_periodic_adv_start(instance); if (rc) log_e("start periodic advertising err: %d", rc); return ESP_OK == rc; } void BLEMultiAdvertising::setDuration(uint8_t instance, int duration, int max_events) { ext_adv[instance] = { instance, duration, max_events }; } #endif // CONFIG_BT_BLE_50_FEATURES_SUPPORTED #endif /* CONFIG_BLUEDROID_ENABLED */