Skip to content

Instantly share code, notes, and snippets.

@Micrified
Created August 30, 2019 12:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Micrified/0145a0a65b334cac8d0a8b0e4aeeee03 to your computer and use it in GitHub Desktop.
Save Micrified/0145a0a65b334cac8d0a8b0e4aeeee03 to your computer and use it in GitHub Desktop.
Bluetooth Source File
#include "ble.h"
/*
*******************************************************************************
* Internal Symbolic Constants *
*******************************************************************************
*/
// Macro: Displays message with error code string
#define ERR(msg,err) { \
fprintf(stderr, "Error (%s:%d) | \"" msg "\" | %s\n", \
__FILE__, __LINE__, esp_err_to_name(err)); \
}
// Macro: Displays message with warning string
#define WARN(msg) { \
fprintf(stderr, "Warning (%s:%d) | \"" msg "\" |\n", \
__FILE__, __LINE__); \
}
// Macro: Displays success message
#define STATUS(msg) { \
fprintf(stderr, "Status (%s:%d) | \"" msg "\" |\n", \
__FILE__, __LINE__); \
}
// Masks the config status in the advertising-status bit-field
#define g_adv_config_status_config_flag (1 << 0)
// Masks the scan response status in the advertising-status bit-field
#define g_adv_config_status_scan_response (1 << 1)
/*
*******************************************************************************
* Internal Forward Declarations *
*******************************************************************************
*/
// GAP Event Handler
static void gap_event_handler (esp_gap_ble_cb_event_t,
esp_ble_gap_cb_param_t *);
// GATTs Event Handler
static void gatts_event_handler (esp_gatts_cb_event_t, esp_gatt_if_t,
esp_ble_gatts_cb_param_t *);
/*****************************************************************************/
// Profile event-handler for WiFi
static void gatts_profile_event_handler_wifi (esp_gatts_cb_event_t,
esp_gatt_if_t, esp_ble_gatts_cb_param_t *);
/*****************************************************************************/
/*
*******************************************************************************
* Internal Global Variables *
*******************************************************************************
*/
// Internal Bit-field marking progression of advertising configuration setup
static uint8_t g_adv_config_status;
// The service UUID that is used the GAP advertising data and scan response
static uint8_t g_service_uuid[16] = {
/* LSB <-----------------------------------------------------------> MSB */
// first uuid, 16bit, [12],[13] is the value
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80,
0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
};
// Universal advertising data structure for GAP
static esp_ble_adv_data_t g_advertising_data = {
.set_scan_rsp = false, // Is a scan response
.include_name = true, // Show device name
.include_txpower = true, // Show transmission power
.min_interval = 6, // Ideal min slave connect interval
.max_interval = 16, // Ideal max slave connect interval
.appearance = 0, // External appearance
.manufacturer_len = 0, // Manufacturer data length
.p_manufacturer_data = NULL, // Manufacturer data
.service_data_len = 0, // Service data length
.p_service_data = NULL, // Service data pointer
.service_uuid_len = 32, // Service UUID length
.p_service_uuid = g_service_uuid,
// Service UUID array pointer
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC |
ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
// Advert discovery mode flags
};
// Universal advertising scan-response structure for GAP
static esp_ble_adv_data_t g_scan_response_data = {
.set_scan_rsp = true, // Is a scan response
.include_name = true, // Show device name
.include_txpower = true, // Show transmission power
.min_interval = 6, // Ideal min slave connect interval
.max_interval = 16, // Ideal max slave connect interval
.appearance = 0, // External appearance
.manufacturer_len = 0, // Manufacturer data length
.p_manufacturer_data = NULL, // Manufacturer data
.service_data_len = 0, // Service data length
.p_service_data = NULL, // Service data pointer
.service_uuid_len = 32, // Service UUID length
.p_service_uuid = g_service_uuid,
// Service UUID array pointer
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC |
ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
// Advert discovery mode flags
};
// Advertising parameters used in esp_ble_gap_start_advertising (for GAP only)
static esp_ble_adv_params_t g_adv_parameters = {
.adv_int_min = 16, // Min advert interval
.adv_int_max = 64, // Max advert interval
.adv_type = ADV_TYPE_IND, // Advertising type
.own_addr_type = BLE_ADDR_TYPE_PUBLIC, // Owner BT addr type
.channel_map = ADV_CHNL_ALL, // Advert channel map
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, // Filter policy
};
// Table of Application Profiles (Add your application profile here)
static struct gatts_profile_t g_profile_table[APP_PROFILE_COUNT] = {
[APP_PROFILE_WIFI] = {
.gatts_cb = gatts_profile_event_handler_wifi,
.gatts_if = ESP_GATT_IF_NONE,
},
};
/*
*******************************************************************************
* Characteristic: WiFi SSID *
*******************************************************************************
*/
// String value WiFi SSID (contains dummy data)
static uint8_t char_str_wifi_ssid[] = {0x11,0x22,0x33};
// A property bit-field for the SSID characteristic
esp_gatt_char_prop_t wifi_property_ssid;
// A permissions bit-field for the SSID characteristic
esp_gatt_perm_t wifi_permissions_ssid;
// The characteristic value for the SSID
static esp_attr_value_t gatts_wifi_char_ssid_value = (esp_attr_value_t) {
.attr_max_len = GATTS_CHARACTERISTIC_VALUE_LENGTH_MAX,
.attr_len = sizeof(char_str_wifi_ssid),
.attr_value = char_str_wifi_ssid
};
// Local variable describing the WiFi SSID buffer
prepare_type_env_t wifi_message_buffer_ssid;
/*
*******************************************************************************
* External Function Definitions *
*******************************************************************************
*/
esp_err_t ble_init (void) {
esp_err_t err = ESP_OK;
// Create Bluetooth controller configuration struct with default settings
esp_bt_controller_config_t btc_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
// Initialize the controller (cfg has stack-size, priority, and baud rate)
if ((err = esp_bt_controller_init(&btc_cfg)) != ESP_OK) {
ERR("Initialization failed", err); goto end;
}
// Enable controller in BLE mode (dual is ESP_BT_MODE_BTDM)
if ((err = esp_bt_controller_enable(ESP_BT_MODE_BLE)) != ESP_OK) {
ERR("Enable BLE mode failed", err); goto end;
}
// Initialize the Bluedroid stack (not same as controller)
if ((err = esp_bluedroid_init()) != ESP_OK) {
ERR("Bluedroid stack init failed", err); goto end;
}
// Enable the Bluedroid stack
if ((err = esp_bluedroid_enable()) != ESP_OK) {
ERR("Bluedroid stack enable failed", err); goto end;
}
// Register the GATTS event handler
if ((err = esp_ble_gatts_register_callback(gatts_event_handler)) != ESP_OK) {
ERR("Register GATTS event handler failed", err); goto end;
}
// Register the GAP event handler
if ((err = esp_ble_gap_register_callback(gap_event_handler)) != ESP_OK) {
ERR("Register GAP event handler failed", err); goto end;
}
/*************************************************************************/
// Register Application Profile: WiFi
if ((err = esp_ble_gatts_app_register(APP_PROFILE_WIFI)) != ESP_OK) {
ERR("Register WiFi Application Profile failed", err); goto end;
}
// Register other profiles here ...
/*************************************************************************/
// Configure Message Transmission Unit size (once per connection)
// if ((err = esp_ble_gatt_set_local_mtu(BLE_MTU_SIZE)) != ESP_OK) {
// ERR("Couldn't set local MTU size", err);
// }
STATUS("Init Complete");
end:
return err;
}
/*
*******************************************************************************
* Internal Function Definitions *
*******************************************************************************
*/
// Establishes connection parameters when invoked in ESP_GATTS_CONNECT_EVT
static esp_ble_conn_update_params_t *gatts_set_connection_parameters
(esp_ble_gatts_cb_param_t *param,uint8_t profile) {
// Connection parameters (static so pseudo-global)
static esp_ble_conn_update_params_t connection_parameters = {
.min_int = 16, // x1.25ms = 20ms
.max_int = 32, // x1.25ms = 40ms
.latency = 4,
.timeout = 400, // x10ms = 4000ms
};
// Copy in the device address field
memcpy(connection_parameters.bda, param->connect.remote_bda,
sizeof(esp_bd_addr_t));
// Update connection ID
g_profile_table[profile].conn_id = param->connect.conn_id;
return &connection_parameters;
}
// Dispatches an indicate (a notification) to a GATT client if needed
static void gatts_send_indicate_response (esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param, esp_gatt_char_prop_t char_property,
uint8_t profile) {
esp_err_t err;
bool needs_confirm = false;
// Response data must be less than the MTU size
uint8_t data_notify[BLE_RSP_MSG_MAX_SIZE] = {0};
uint8_t data_indicate[BLE_RSP_MSG_MAX_SIZE] = {0};
uint8_t *data_out = NULL;
// Reconstruct the mode flag for notifications
uint16_t mode = param->write.value[1] << 8 | param->write.value[0];
// Handle received mode type
switch (mode) {
case 0: return; // Send no response - exit
case 1: data_out = data_notify; // Send response without confirm
break;
case 2: needs_confirm = true; // Send response with confirm
data_out = data_indicate;
break;
default: // Unrecognized response mode
ERR("Unknown descriptor mode received", ESP_FAIL);
return;
}
// Dispatch response
if ((err = esp_ble_gatts_send_indicate(
gatts_if,
param->write.conn_id,
g_profile_table[profile].char_handle,
BLE_RSP_MSG_MAX_SIZE * sizeof(uint8_t),
data_out,
needs_confirm)) != ESP_OK) {
ERR("Couldn't send indicate/notify response", err);
}
}
// Handles long-characteristic write-event
void gatts_write_event_long_handler (esp_gatt_if_t gatts_if,
prepare_type_env_t *write_buffer, esp_ble_gatts_cb_param_t *param) {
esp_gatt_status_t status = ESP_GATT_OK;
esp_gatt_rsp_t *gatt_rsp = NULL;
esp_err_t err;
// Allocate memory for long-write
if (write_buffer->buffer == NULL) {
write_buffer->buffer = malloc(GATTS_WRITE_EVENT_MAX_BUF_SIZE *
sizeof(uint8_t));
if (write_buffer->buffer == NULL) {
status = ESP_GATT_NO_RESOURCES;
goto respond;
}
}
// Validate offset
if (param->write.offset > GATTS_WRITE_EVENT_MAX_BUF_SIZE) {
status = ESP_GATT_INVALID_OFFSET;
goto respond;
}
// Validate length
if ((param->write.offset + param->write.len) >
GATTS_WRITE_EVENT_MAX_BUF_SIZE) {
status = ESP_GATT_INVALID_ATTR_LEN;
goto respond;
}
// Allocate response
if ((gatt_rsp = malloc(sizeof(esp_gatt_rsp_t))) == NULL) {
status = ESP_GATT_NO_RESOURCES;
goto respond;
}
// Set response attributes
gatt_rsp->attr_value.len = param->write.len;
gatt_rsp->attr_value.handle = param->write.handle;
gatt_rsp->attr_value.offset = param->write.offset;
gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE; // Set auth here
// Copy parameter write value into response (echo basically)
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
respond:
// Dispatch response
if ((err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
param->write.trans_id, status, gatt_rsp)) != ESP_OK) {
ERR("Couldn't send write-event response", err);
}
// Return now if an error occurred before
if (status != ESP_GATT_OK) return;
// Free allocated response memory
free(gatt_rsp);
// Copy received data into message buffer (after response send)
memcpy(write_buffer->buffer + param->write.offset, param->write.value,
param->write.len);
}
// Handles all write-events that don't include indications/notify responses
void gatts_write_event_handler (esp_gatt_if_t gatts_if,
prepare_type_env_t *write_buffer, esp_ble_gatts_cb_param_t *param) {
esp_err_t err;
// If a response isn't needed - exit
if (param->write.need_rsp == 0) return;
// Invoke long-write handler if necessary
if (param->write.is_prep != 0) {
gatts_write_event_long_handler(gatts_if, write_buffer, param);
return;
}
// Otherwise send response immediately
if ((err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
param->write.trans_id, ESP_GATT_OK, NULL)) != ESP_OK) {
ERR("Couldn't send write-event response", err);
}
return;
}
// Handles the executive-write message (marks the end of a long-write operation)
void gatts_write_exec_handler (prepare_type_env_t *write_buffer,
esp_ble_gatts_cb_param_t *param) {
// Check if the write was cancelled
if (param->exec_write.exec_write_flag != ESP_GATT_PREP_WRITE_EXEC) {
STATUS("A long-write was cancelled");
} else {
STATUS("Long-write confirmed");
}
// Reset the write buffer
if (write_buffer->buffer != NULL) {
free(write_buffer->buffer);
write_buffer->buffer = NULL;
}
write_buffer->len = 0;
}
/*
*******************************************************************************
* GAP/GATTS Event Handler Definitions *
*******************************************************************************
*/
// GAP Event Handler
static void gap_event_handler (esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t *param) {
esp_err_t err;
switch (event) {
// Event toggled when advertising data is set
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: {
// Delete the set config_flag. Keep others unchanged
g_adv_config_status &= (~g_adv_config_status_config_flag);
// If any other flags are set, abort
if (g_adv_config_status != 0) {
break;
}
// Otherwise begin advertising
if ((err = esp_ble_gap_start_advertising(&g_adv_parameters))
!= ESP_OK) {
ERR("Couldn't set advert parameters", err);
}
STATUS("Advertising data was set");
}
break;
// Event toggled when the scan response is set
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: {
// Delete the scan response flag. Keep others unchanged
g_adv_config_status &= (~g_adv_config_status_scan_response);
// If any other flags are set, abort
if (g_adv_config_status != 0) {
break;
}
// Otherwise begin advertising
if ((err = esp_ble_gap_start_advertising(&g_adv_parameters))
!= ESP_OK) {
ERR("Couldn't set advert parameters", err);
}
}
break;
// Event toggled when advertising has started successfully
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: {
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ERR("Advertising failed to start", ESP_OK);
} else {
STATUS("Advertising started successfully");
}
}
break;
// Event triggered if the connection parameters were updated
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: {
STATUS("Connection parameters updated");
}
break;
default: {
fprintf(stderr, "WARN: Unknown Event: %d\n", event);
}
break;
}
}
// GATTs Event Handler
static void gatts_event_handler (esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
int i;
// If its a registration event - we register the interface for the profile
if (event == ESP_GATTS_REG_EVT) {
// If registration failed, output an error
if (param->reg.status != ESP_GATT_OK) {
ERR("Profile registration failed", ESP_OK);
return;
}
// Otherwise save the interface in the table
g_profile_table[param->reg.app_id].gatts_if = gatts_if;
STATUS("Profile registration succeeded");
return;
}
// Otherwise it's not a registration event. Forward to matching profile
for (i = 0; i < APP_PROFILE_COUNT; ++i) {
// Ignore entries that don't have a matching or available interface
if (gatts_if != ESP_GATT_IF_NONE &&
gatts_if != g_profile_table[i].gatts_if) {
continue;
}
// Ignore entries that don't have an assigned callback
if (g_profile_table[i].gatts_cb == NULL) {
continue;
}
// Forward event to profile callback
g_profile_table[i].gatts_cb(event, gatts_if, param);
}
// If it belonged to no profile - issue a warning
WARN("Event designated for unregistered profile!?");
}
/*
*******************************************************************************
* Profile Handler Definitions *
*******************************************************************************
*/
// Handler for events concerning the GATTS WiFi profile
static void gatts_profile_event_handler_wifi (esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
esp_err_t err;
switch (event) {
// Event for configuring advertising parameters for the specific profile
case ESP_GATTS_REG_EVT: {
// Set as primary service ID
g_profile_table[APP_PROFILE_WIFI].service_id.is_primary = true;
// The instance ID distinguishes multiple services with same ID.
// Since we have only one service, it gets ID 0
g_profile_table[APP_PROFILE_WIFI].service_id.id.inst_id = 0;
// Set the length of the ID
g_profile_table[APP_PROFILE_WIFI].service_id.id.uuid.len =
ESP_UUID_LEN_16;
// Set the UUID value.
g_profile_table[APP_PROFILE_WIFI].service_id.id.uuid.uuid.uuid16 =
GATTS_SERVICE_UUID_WIFI;
// Set the device name
esp_ble_gap_set_device_name(BLE_DEVICE_NAME);
// Configure the advertising data
if ((err = esp_ble_gap_config_adv_data(&g_advertising_data))
!= ESP_OK) {
ERR("Couldn't set advertising data", err);
break;
}
// Mark advert data config complete
g_adv_config_status |= g_adv_config_status_config_flag;
// Configure the scan response data
if ((err = esp_ble_gap_config_adv_data(&g_scan_response_data))
!= ESP_OK) {
ERR("Couldn't set scan response", err);
break;
}
// Mark scan response config complete
g_adv_config_status |= g_adv_config_status_scan_response;
// Attempt to create the service and attribute table
if ((err = esp_ble_gatts_create_service(gatts_if,
&g_profile_table[APP_PROFILE_WIFI].service_id,
GATTS_HANDLE_COUNT_WIFI)) != ESP_OK) {
ERR("Setting profile attribute table failed", err);
}
}
break;
// Event where you now add your characteristics after creating profile
case ESP_GATTS_CREATE_EVT: {
// Set the service handle
g_profile_table[APP_PROFILE_WIFI].service_handle =
param->create.service_handle;
// Set the characteristic UUID length
g_profile_table[APP_PROFILE_WIFI].char_uuid.len = ESP_UUID_LEN_16;
// Set the UUID for the characteristic
g_profile_table[APP_PROFILE_WIFI].char_uuid.uuid.uuid16 =
GATTS_SERVICE_UUID_WIFI_SSID;
// Try starting the service
if ((err = esp_ble_gatts_start_service(
g_profile_table[APP_PROFILE_WIFI].service_handle)) != ESP_OK) {
ERR("Could not start service", err);
break;
}
// Characteristic properties are those shown to the client only
wifi_property_ssid = ESP_GATT_CHAR_PROP_BIT_READ |
ESP_GATT_CHAR_PROP_BIT_WRITE |
ESP_GATT_CHAR_PROP_BIT_NOTIFY;
// Characteristic permissions are those actually enforced
wifi_permissions_ssid = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE;
// Add the characteristic; set Attribute Response Control
if ((err = esp_ble_gatts_add_char(
g_profile_table[APP_PROFILE_WIFI].service_handle,
&g_profile_table[APP_PROFILE_WIFI].char_uuid,
wifi_permissions_ssid,
wifi_property_ssid,
&gatts_wifi_char_ssid_value,
NULL)) != ESP_OK) { // NULL means event-handling is manual
ERR("Could not add characteristic", err);
break;
}
}
break;
// Event tripped when the gatts_start_service function succeeds
case ESP_GATTS_START_EVT: {
STATUS("Service started successfully");
}
break;
// Event tripped by adding characteristic
case ESP_GATTS_ADD_CHAR_EVT: {
uint16_t len; const uint8_t *characteristic_p;
// Check that the characteristic was added successfully
if ((err = esp_ble_gatts_get_attr_value(
param->add_char.attr_handle,
&len,
&characteristic_p)) != ESP_OK) {
ERR("Failed to add characteristic", err);
break;
}
// Add the characteristic descriptor if characteristic established
if ((err = esp_ble_gatts_add_char_descr(
g_profile_table[APP_PROFILE_WIFI].service_handle,
&g_profile_table[APP_PROFILE_WIFI].descr_uuid,
wifi_permissions_ssid,
NULL, // Initial value for characteristic descriptor
NULL // Auto response parameter
)) != ESP_OK) {
ERR("Could not add characteristic descriptor", err);
break;
}
}
break;
// Event tripped by adding characteristic descriptor
case ESP_GATTS_ADD_CHAR_DESCR_EVT: {
STATUS("Characteristic descriptor added successfully");
}
break;
// Event triggered when someone connects to the GATT server
case ESP_GATTS_CONNECT_EVT: {
// Note: Only needs to be done ONCE for ALL PROFILES
// Connection parameters are offloaded and set here
esp_ble_conn_update_params_t *conn_p =
gatts_set_connection_parameters(param, APP_PROFILE_WIFI);
// Apply update to connection parameters
if ((err = esp_ble_gap_update_conn_params(conn_p)) != ESP_OK) {
ERR("Could not update connection parameters", err);
}
}
break;
// Event tripped by READ operation: Implemented if auto-resp is NULL
case ESP_GATTS_READ_EVT: {
esp_gatt_rsp_t response_data;
// Configure response data (with junk payload)
memset(&response_data, 0, sizeof(esp_gatt_rsp_t));
response_data.attr_value.handle = param->read.handle;
response_data.attr_value.len = 4;
response_data.attr_value.value[0] = 0xde;
response_data.attr_value.value[1] = 0xed;
response_data.attr_value.value[2] = 0xbe;
response_data.attr_value.value[3] = 0xef;
// Dispatch response
if ((err = esp_ble_gatts_send_response(
gatts_if,
param->read.conn_id,
param->read.trans_id,
ESP_GATT_OK,
&response_data
)) != ESP_OK) {
ERR("READ response failed to send", err);
break;
}
}
break;
// Event tripped by WRITE operation: Implemented if auto-resp is NULL
case ESP_GATTS_WRITE_EVT: {
/* Two kinds of write-requests
* 1. Write Characteristic Value: (1 MTU ~= 23 bytes)
* 2. Write Long Characteristic Value: (? MTU ~= ?? bytes)
* Request type (2) takes several messages to transmit and receive
* and to confirm it you must perfrom an executive write request
*/
// Send an indication if (1) not a long write (2) is descr message
if (param->write.is_prep == 0 &&
g_profile_table[APP_PROFILE_WIFI].descr_handle == param->write.handle &&
param->write.len == 2
) {
// This is sent for a specific characteristic
gatts_send_indicate_response(gatts_if, param, wifi_property_ssid,
APP_PROFILE_WIFI);
}
// Invoke the handler for write-events (characteristic specific)
gatts_write_event_handler(gatts_if, &wifi_message_buffer_ssid,
param);
}
break;
// Event tripped when a long-write has finished
case ESP_GATTS_EXEC_WRITE_EVT: {
// Try sending some kind of response
if ((err = esp_ble_gatts_send_response(
gatts_if,
param->write.conn_id,
param->write.trans_id,
ESP_GATT_OK,
NULL)) != ESP_OK) {
ERR("Couldn't send exec message response", err);
}
// Invoke executive write function (long write ended)
gatts_write_exec_handler(&wifi_message_buffer_ssid, param);
}
break;
default: {
fprintf(stderr, "WARN: Unknown Event: %d\n", event);
}
break;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment