Protocomm : Added support for choice of transport - WiFi (SoftAP+HTTPD), BLE, Console (development friendly transport)

Co-Authored-By: Amey Inamdar <amey@espressif.com>
Co-Authored-By: Anurag Kar <anurag.kar@espressif.com>
This commit is contained in:
Amey Inamdar 2018-07-30 21:36:48 +05:30 committed by Anurag Kar
parent e94dffc9c5
commit 9428375368
10 changed files with 1614 additions and 3 deletions

View File

@ -0,0 +1,25 @@
set(COMPONENT_ADD_INCLUDEDIRS include/common
include/security
include/transports)
set(COMPONENT_PRIV_INCLUDEDIRS proto-c src/common src/simple_ble)
set(COMPONENT_SRCS "src/common/protocomm.c"
"src/security/security0.c"
"src/security/security1.c"
"proto-c/constants.pb-c.c"
"proto-c/sec0.pb-c.c"
"proto-c/sec1.pb-c.c"
"proto-c/session.pb-c.c"
"src/transports/protocomm_console.c"
"src/transports/protocomm_httpd.c")
set(COMPONENT_PRIV_REQUIRES protobuf-c mbedtls console http_server bt)
if(CONFIG_BT_ENABLED)
if(CONFIG_BLUEDROID_ENABLED)
list(APPEND COMPONENT_SRCS
"src/simple_ble/simple_ble.c"
"src/transports/protocomm_ble.c")
endif()
endif()
register_component()

View File

@ -1,3 +1,7 @@
COMPONENT_ADD_INCLUDEDIRS := include/common include/security proto-c
COMPONENT_PRIV_INCLUDEDIRS := src/common
COMPONENT_SRCDIRS := src/common src/security proto-c
COMPONENT_ADD_INCLUDEDIRS := include/common include/security include/transports
COMPONENT_PRIV_INCLUDEDIRS := proto-c src/common src/simple_ble
COMPONENT_SRCDIRS := src/common src/security proto-c src/simple_ble src/transports
ifneq ($(filter y, $(CONFIG_BT_ENABLED) $(CONFIG_BLUEDROID_ENABLED)),y y)
COMPONENT_OBJEXCLUDE := src/simple_ble/simple_ble.o src/transports/protocomm_ble.o
endif

View File

@ -0,0 +1,92 @@
// Copyright 2018 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.
#pragma once
#include <protocomm.h>
/**
* BLE device name cannot be larger than this value
*/
#define MAX_BLE_DEVNAME_LEN 13
/**
* @brief This structure maps handler required by protocomm layer to
* UUIDs which are used to uniquely identify BLE characteristics
* from a smartphone or a similar client device.
*/
typedef struct name_uuid {
/**
* Name of the handler, which is passed to protocomm layer
*/
char *name;
/**
* UUID to be assigned to the BLE characteristic which is
* mapped to the handler
*/
uint16_t uuid;
} protocomm_ble_name_uuid_t;
/**
* @brief Config parameters for protocomm BLE service
*/
typedef struct {
/**
* BLE device name being broadcast at the time of provisioning
*/
char device_name[MAX_BLE_DEVNAME_LEN];
uint8_t service_uuid[16]; /*!< SSID of the provisioning service */
ssize_t nu_lookup_count; /*!< Number of entries in the Name-UUID lookup table */
/**
* Pointer to the Name-UUID lookup table
*/
protocomm_ble_name_uuid_t *nu_lookup;
} protocomm_ble_config_t;
/**
* @brief Start Bluetooth Low Energy based transport layer for provisioning
*
* Initialize and start required BLE service for provisioning. This includes
* the initialization for characteristics/service for BLE.
*
* @param[in] pc Protocomm instance pointer obtained from protocomm_new()
* @param[in] config Pointer to config structure for initialising BLE
*
* @return
* - ESP_OK : if successful
* - ESP_FAIL : Simple BLE start error
* - ESP_ERR_NO_MEM : Error allocating memory for internal resources
* - ESP_ERR_INVALID_STATE : Error in ble config
* - ESP_ERR_INVALID_ARG : Null arguments
*/
esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config);
/**
* @brief Stop Bluetooth Low Energy based transport layer for provisioning
*
* Stops service/task responsible for BLE based interactions for provisioning
*
* @note You might want to optionally reclaim memory from Bluetooth.
* Refer to the documentation of `esp_bt_mem_release` in that case.
*
* @param[in] pc Same protocomm instance that was passed to protocomm_ble_start()
*
* @return
* - ESP_OK : For success or appropriate error code
* - ESP_FAIL : Simple BLE stop error
* - ESP_ERR_INVALID_ARG : Null / incorrect protocomm instance
*/
esp_err_t protocomm_ble_stop(protocomm_t *pc);

View File

@ -0,0 +1,59 @@
// Copyright 2018 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.
#pragma once
#include <protocomm.h>
#define PROTOCOMM_CONSOLE_DEFAULT_CONFIG() { \
.stack_size = 4096, \
.task_priority = tskIDLE_PRIORITY + 3, \
}
/**
* @brief Config parameters for protocomm console
*/
typedef struct {
size_t stack_size; /*!< Stack size of console taks */
unsigned task_priority; /*!< Priority of console task */
} protocomm_console_config_t;
/**
* @brief Start console based protocomm transport
*
* @note This is a singleton. ie. Protocomm can have multiple instances, but only
* one instance can be bound to a console based transport layer.
*
* @param[in] pc Protocomm instance pointer obtained from protocomm_new()
* @param[in] config Config param structure for protocomm console
*
* @return
* - ESP_OK : Server started succefully
* - ESP_ERR_INVALID_ARG : Null arguments
* - ESP_ERR_NOT_SUPPORTED : Transport layer bound to another protocomm instance
* - ESP_ERR_INVALID_STATE : Transport layer already bound to this protocomm instance
* - ESP_FAIL : Failed to start console thread
*/
esp_err_t protocomm_console_start(protocomm_t *pc, const protocomm_console_config_t *config);
/**
* @brief Stop console protocomm transport
*
* @param[in] pc Same protocomm instance that was passed to protocomm_console_start()
*
* @return
* - ESP_OK : Server stopped succefully
* - ESP_ERR_INVALID_ARG : Null / incorrect protocomm instance pointer
*/
esp_err_t protocomm_console_stop(protocomm_t *pc);

View File

@ -0,0 +1,73 @@
// Copyright 2018 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.
#pragma once
#include <protocomm.h>
#define PROTOCOMM_HTTPD_DEFAULT_CONFIG() { \
.port = 80, \
.stack_size = 4096, \
.task_priority = tskIDLE_PRIORITY + 5, \
}
/**
* @brief Config parameters for protocomm HTTP server
*/
typedef struct {
uint16_t port; /*!< Port on which the http server will listen */
/**
* Stack size of server task, adjusted depending
* upon stack usage of endpoint handler
*/
size_t stack_size;
unsigned task_priority; /*!< Priority of server task */
} protocomm_httpd_config_t;
/**
* @brief Start HTTPD protocomm transport
*
* This API internally creates a framework to allow endpoint registration and security
* configuration for the protocomm.
*
* @note This is a singleton. ie. Protocomm can have multiple instances, but only
* one instance can be bound to an HTTP transport layer.
*
* @param[in] pc Protocomm instance pointer obtained from protocomm_new()
* @param[in] config Pointer to config structure for initialising HTTP server
*
* @return
* - ESP_OK : Server started succefully
* - ESP_ERR_INVALID_ARG : Null arguments
* - ESP_ERR_NOT_SUPPORTED : Transport layer bound to another protocomm instance
* - ESP_ERR_INVALID_STATE : Transport layer already bound to this protocomm instance
* - ESP_ERR_NO_MEM : Memory allocation for server resource failed
* - ESP_ERR_HTTPD_* : HTTP server error on start
*/
esp_err_t protocomm_httpd_start(protocomm_t *pc, const protocomm_httpd_config_t *config);
/**
* @brief Stop HTTPD protocomm transport
*
* This API cleans up the HTTPD transport protocomm and frees all the handlers registered
* with the protocomm.
*
* @param[in] pc Same protocomm instance that was passed to protocomm_httpd_start()
*
* @return
* - ESP_OK : Server stopped succefully
* - ESP_ERR_INVALID_ARG : Null / incorrect protocomm instance pointer
*/
esp_err_t protocomm_httpd_stop(protocomm_t *pc);

View File

@ -0,0 +1,268 @@
// Copyright 2015-2018 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.
#include <freertos/FreeRTOS.h>
#include <esp_system.h>
#include <esp_log.h>
#include <esp_bt.h>
#include <esp_gap_ble_api.h>
#include <esp_gatts_api.h>
#include <esp_bt_main.h>
#include <esp_gatt_common_api.h>
#include "simple_ble.h"
#define SIMPLE_BLE_MAX_GATT_TABLE_SIZE 20
static const char *TAG = "simple_ble";
static simple_ble_cfg_t *g_ble_cfg_p;
static uint16_t g_gatt_table_map[SIMPLE_BLE_MAX_GATT_TABLE_SIZE];
uint16_t simple_ble_get_uuid(uint16_t handle)
{
uint16_t *uuid_ptr;
for (int i = 0; i < SIMPLE_BLE_MAX_GATT_TABLE_SIZE; i++) {
if (g_gatt_table_map[i] == handle) {
uuid_ptr = (uint16_t *) g_ble_cfg_p->gatt_db[i].att_desc.uuid_p;
return *uuid_ptr;
}
}
return -1;
}
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params);
break;
default:
break;
}
}
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t p_gatts_if, esp_ble_gatts_cb_param_t *param)
{
static esp_gatt_if_t gatts_if = ESP_GATT_IF_NONE;
esp_err_t ret;
uint8_t service_instance_id = 0;
if (event == ESP_GATTS_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
gatts_if = p_gatts_if;
} else {
ESP_LOGE(TAG, "reg app failed, app_id 0x0x%x, status %d",
param->reg.app_id,
param->reg.status);
return;
}
}
if (gatts_if != ESP_GATT_IF_NONE && gatts_if != p_gatts_if) {
return;
}
switch (event) {
case ESP_GATTS_REG_EVT:
ret = esp_ble_gatts_create_attr_tab(g_ble_cfg_p->gatt_db, gatts_if, g_ble_cfg_p->gatt_db_count, service_instance_id);
if (ret) {
ESP_LOGE(TAG, "create attr table failed, error code = 0x%x", ret);
return;
}
ret = esp_ble_gap_set_device_name(g_ble_cfg_p->device_name);
if (ret) {
ESP_LOGE(TAG, "set device name failed, error code = 0x%x", ret);
return;
}
ret = esp_ble_gap_config_adv_data(&g_ble_cfg_p->adv_data);
if (ret) {
ESP_LOGE(TAG, "config adv data failed, error code = 0x%x", ret);
return;
}
break;
case ESP_GATTS_READ_EVT:
g_ble_cfg_p->read_fn(event, gatts_if, param);
break;
case ESP_GATTS_WRITE_EVT:
g_ble_cfg_p->write_fn(event, gatts_if, param);
break;
case ESP_GATTS_EXEC_WRITE_EVT:
g_ble_cfg_p->exec_write_fn(event, gatts_if, param);
break;
case ESP_GATTS_MTU_EVT:
ESP_LOGV(TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
if (g_ble_cfg_p->set_mtu_fn) {
g_ble_cfg_p->set_mtu_fn(event, gatts_if, param);
}
break;
case ESP_GATTS_CONF_EVT:
ESP_LOGD(TAG, "ESP_GATTS_CONF_EVT, status = %d", param->conf.status);
break;
case ESP_GATTS_START_EVT:
ESP_LOGD(TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle);
break;
case ESP_GATTS_CONNECT_EVT:
ESP_LOGD(TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id);
g_ble_cfg_p->connect_fn(event, gatts_if, param);
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
/* For the iOS system, please refer the official Apple documents about BLE connection parameters restrictions. */
conn_params.latency = 0;
conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms
conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms
conn_params.timeout = 400; // timeout = 400*10ms = 4000ms
esp_ble_gap_update_conn_params(&conn_params);
break;
case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGD(TAG, "ESP_GATTS_DISCONNECT_EVT, reason = %d", param->disconnect.reason);
g_ble_cfg_p->disconnect_fn(event, gatts_if, param);
esp_ble_gap_start_advertising(&g_ble_cfg_p->adv_params);
break;
case ESP_GATTS_CREAT_ATTR_TAB_EVT: {
if (param->add_attr_tab.status != ESP_GATT_OK) {
ESP_LOGE(TAG, "creating the attribute table failed, error code=0x%x", param->add_attr_tab.status);
} else if (param->add_attr_tab.num_handle != g_ble_cfg_p->gatt_db_count) {
ESP_LOGE(TAG, "created attribute table abnormally ");
} else {
ESP_LOGD(TAG, "created attribute table successfully, the number handle = %d", param->add_attr_tab.num_handle);
memcpy(g_gatt_table_map, param->add_attr_tab.handles, param->add_attr_tab.num_handle * sizeof(g_gatt_table_map[0]));
/* We assume, for now, that the first entry is always the index to the 'service' definition */
esp_ble_gatts_start_service(g_gatt_table_map[0]);
}
break;
}
case ESP_GATTS_STOP_EVT:
case ESP_GATTS_OPEN_EVT:
case ESP_GATTS_CANCEL_OPEN_EVT:
case ESP_GATTS_CLOSE_EVT:
case ESP_GATTS_LISTEN_EVT:
case ESP_GATTS_CONGEST_EVT:
case ESP_GATTS_UNREG_EVT:
case ESP_GATTS_DELETE_EVT:
default:
break;
}
}
simple_ble_cfg_t *simple_ble_init()
{
simple_ble_cfg_t *ble_cfg_p = (simple_ble_cfg_t *) malloc(sizeof(simple_ble_cfg_t));
if (ble_cfg_p == NULL) {
ESP_LOGE(TAG, "No memory for simple_ble_cfg_t");
return NULL;
}
return ble_cfg_p;
}
esp_err_t simple_ble_deinit()
{
free(g_ble_cfg_p->gatt_db);
free(g_ble_cfg_p);
g_ble_cfg_p = NULL;
return ESP_OK;
}
/* Expects the pointer stays valid throughout */
esp_err_t simple_ble_start(simple_ble_cfg_t *cfg)
{
g_ble_cfg_p = cfg;
ESP_LOGD(TAG, "Free mem at start of simple_ble_init %d", esp_get_free_heap_size());
esp_err_t ret;
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(TAG, "%s enable controller failed %d", __func__, ret);
return ret;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(TAG, "%s enable controller failed %d", __func__, ret);
return ret;
}
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(TAG, "%s init bluetooth failed %d", __func__, ret);
return ret;
}
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(TAG, "%s enable bluetooth failed %d", __func__, ret);
return ret;
}
ret = esp_ble_gatts_register_callback(gatts_profile_event_handler);
if (ret) {
ESP_LOGE(TAG, "gatts register error, error code = 0x%x", ret);
return ret;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret) {
ESP_LOGE(TAG, "gap register error, error code = 0x%x", ret);
return ret;
}
uint16_t app_id = 0x55;
ret = esp_ble_gatts_app_register(app_id);
if (ret) {
ESP_LOGE(TAG, "gatts app register error, error code = 0x%x", ret);
return ret;
}
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
if (local_mtu_ret) {
ESP_LOGE(TAG, "set local MTU failed, error code = 0x%x", local_mtu_ret);
}
ESP_LOGD(TAG, "Free mem at end of simple_ble_init %d", esp_get_free_heap_size());
return ESP_OK;
}
esp_err_t simple_ble_stop()
{
esp_err_t err;
ESP_LOGD(TAG, "Free mem at start of simple_ble_stop %d", esp_get_free_heap_size());
err = esp_bluedroid_disable();
if (err != ESP_OK) {
return ESP_FAIL;
}
ESP_LOGD(TAG, "esp_bluedroid_disable called successfully");
err = esp_bluedroid_deinit();
if (err != ESP_OK) {
return err;
}
ESP_LOGD(TAG, "esp_bluedroid_deinit called successfully");
err = esp_bt_controller_disable();
if (err != ESP_OK) {
return ESP_FAIL;
}
/* The API `esp_bt_controller_deinit` will have to be removed when we add support for
* `reset to provisioning`
*/
ESP_LOGD(TAG, "esp_bt_controller_disable called successfully");
err = esp_bt_controller_deinit();
if (err != ESP_OK) {
return ESP_FAIL;
}
ESP_LOGD(TAG, "esp_bt_controller_deinit called successfully");
ESP_LOGD(TAG, "Free mem at end of simple_ble_stop %d", esp_get_free_heap_size());
return ESP_OK;
}

View File

@ -0,0 +1,107 @@
// Copyright 2015-2018 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 _SIMPLE_BLE_
#define _SIMPLE_BLE_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <esp_gap_ble_api.h>
#include <esp_gatts_api.h>
typedef void (simple_ble_cb_t)(esp_gatts_cb_event_t event, esp_gatt_if_t p_gatts_if, esp_ble_gatts_cb_param_t *param);
/**
* This structure is populated with the details required
* to create an instance of BLE easily. It requires function
* pointers, advertising parameters and gatt description table
*/
typedef struct {
/** Name to be displayed to devices scanning for ESP32 */
const char *device_name;
/** Advertising data content, according to "Supplement to the Bluetooth Core Specification" */
esp_ble_adv_data_t adv_data;
/** Parameters to configure the nature of advertising */
esp_ble_adv_params_t adv_params;
/** Descriptor table which consists the configuration required by services and characteristics */
esp_gatts_attr_db_t *gatt_db;
/** Number of entries in the gatt_db descriptor table */
ssize_t gatt_db_count;
/** BLE read callback */
simple_ble_cb_t *read_fn;
/** BLE write callback */
simple_ble_cb_t *write_fn;
/** BLE exec write callback */
simple_ble_cb_t *exec_write_fn;
/** Client disconnect callback */
simple_ble_cb_t *disconnect_fn;
/** Client connect callback */
simple_ble_cb_t *connect_fn;
/** MTU set callback */
simple_ble_cb_t *set_mtu_fn;
} simple_ble_cfg_t;
/** Initialise a simple ble connection
*
* This function allocates memory and returns a pointer to the
* configuration structure.
*
* @return simple_ble_cfg_t* Pointer to configuration structure
*/
simple_ble_cfg_t *simple_ble_init();
/** Deallocates memory
*
* This function deallocate memory of the configuration structure.
*
* @return ESP_OK
*/
esp_err_t simple_ble_deinit();
/** Starts BLE service
*
* This function makes calls to the GATT and GAP APIs
* to initialize the BLE service as per parameters stored
* in the config structure. At the end of this function,
* one should be able to scan and connect to the ESP32 device
* using BLE.
* This API sets the MTU size to 500 (this is not part of the config structure)
*
* @return ESP_OK on success, and appropriate error code for failure
*/
esp_err_t simple_ble_start(simple_ble_cfg_t *cfg);
/** Stops the BLE service
*
* This API is called to stop the BLE service.
* This includes calls to disable and deinit bluedroid and bt controller.
*
* @return ESP_OK on success, and appropriate error code for failure
*/
esp_err_t simple_ble_stop();
/** Convert handle to UUID of characteristic
*
* This function can be easily used to get the corresponding
* UUID for a characteristic that has been created, and the one for
* which we only have the handle for.
*
* @return uuid the UUID of the handle, -1 in case of invalid handle
*/
uint16_t simple_ble_get_uuid(uint16_t handle);
#endif /* _SIMPLE_BLE_ */

View File

@ -0,0 +1,510 @@
// Copyright 2018 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.
#include <sys/param.h>
#include <esp_log.h>
#include <esp_gatt_common_api.h>
#include <protocomm.h>
#include <protocomm_ble.h>
#include "protocomm_priv.h"
#include "simple_ble.h"
#define CHAR_VAL_LEN_MAX (256 + 1)
#define PREPARE_BUF_MAX_SIZE CHAR_VAL_LEN_MAX
static const char *TAG = "protocomm_ble";
/* BLE specific configuration parameters */
const uint16_t GATTS_SERVICE_UUID_PROV = 0xFFFF;
const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE;
typedef struct {
uint8_t *prepare_buf;
int prepare_len;
uint16_t handle;
} prepare_type_env_t;
static prepare_type_env_t prepare_write_env;
typedef struct _protocomm_ble {
protocomm_t *pc_ble;
protocomm_ble_name_uuid_t *g_nu_lookup;
ssize_t g_nu_lookup_count;
uint16_t gatt_mtu;
} _protocomm_ble_internal_t;
static _protocomm_ble_internal_t *protoble_internal;
static esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x100,
.adv_int_max = 0x100,
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
static char* protocomm_ble_device_name = NULL;
/* The length of adv data must be less than 31 bytes */
static esp_ble_adv_data_t adv_data = {
.set_scan_rsp = false,
.include_name = true,
.include_txpower = true,
.min_interval = 0x100,
.max_interval = 0x100,
.appearance = ESP_BLE_APPEARANCE_UNKNOWN,
.manufacturer_len = 0,
.p_manufacturer_data = NULL,
.service_data_len = 0,
.p_service_data = NULL,
.service_uuid_len = 0,
.p_service_uuid = NULL,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
static void hexdump(const char *msg, uint8_t *buf, int len)
{
ESP_LOGD(TAG, "%s:", msg);
ESP_LOG_BUFFER_HEX_LEVEL(TAG, buf, len, ESP_LOG_DEBUG);
}
static const char *handle_to_handler(uint16_t handle)
{
uint16_t uuid = simple_ble_get_uuid(handle);
for (int i = 0; i < protoble_internal->g_nu_lookup_count; i++) {
if (protoble_internal->g_nu_lookup[i].uuid == uuid ) {
return protoble_internal->g_nu_lookup[i].name;
}
}
return NULL;
}
static void transport_simple_ble_read(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
static const uint8_t *read_buf = NULL;
static uint16_t read_len = 0;
esp_gatt_status_t status = ESP_OK;
ESP_LOGD(TAG, "Inside read w/ session - %d on param %d %d",
param->read.conn_id, param->read.handle, read_len);
if (!read_len) {
ESP_LOGD(TAG, "Reading attr value first time");
status = esp_ble_gatts_get_attr_value(param->read.handle, &read_len, &read_buf);
} else {
ESP_LOGD(TAG, "Subsequent read request for attr value");
}
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *) malloc(sizeof(esp_gatt_rsp_t));
if (gatt_rsp != NULL) {
gatt_rsp->attr_value.len = MIN(read_len, (protoble_internal->gatt_mtu - 1));
if (read_len && read_buf) {
memcpy(gatt_rsp->attr_value.value,
read_buf + param->read.offset,
gatt_rsp->attr_value.len);
}
read_len -= gatt_rsp->attr_value.len;
} else {
ESP_LOGE(TAG, "%s, malloc failed", __func__);
return;
}
esp_err_t err = esp_ble_gatts_send_response(gatts_if, param->read.conn_id,
param->read.trans_id, status, gatt_rsp);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Send response error in read");
}
free(gatt_rsp);
}
static esp_err_t prepare_write_event_env(esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param)
{
ESP_LOGD(TAG, "prepare write, handle = %d, value len = %d",
param->write.handle, param->write.len);
esp_gatt_status_t status = ESP_GATT_OK;
if (prepare_write_env.prepare_buf == NULL) {
prepare_write_env.prepare_buf = (uint8_t *) malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
if (prepare_write_env.prepare_buf == NULL) {
ESP_LOGE(TAG, "%s , failed tp allocate preparebuf", __func__);
status = ESP_GATT_NO_RESOURCES;
}
/* prepare_write_env.prepare_len = 0; */
} else {
if (param->write.offset > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_OFFSET;
} else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_ATTR_LEN;
}
}
memcpy(prepare_write_env.prepare_buf + param->write.offset,
param->write.value,
param->write.len);
prepare_write_env.prepare_len += param->write.len;
prepare_write_env.handle = param->write.handle;
if (param->write.need_rsp) {
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *) malloc(sizeof(esp_gatt_rsp_t));
if (gatt_rsp != NULL) {
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;
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
param->write.trans_id, status,
gatt_rsp);
if (response_err != ESP_OK) {
ESP_LOGE(TAG, "Send response error in prep write");
}
free(gatt_rsp);
} else {
ESP_LOGE(TAG, "%s, malloc failed", __func__);
}
}
if (status != ESP_GATT_OK) {
if (prepare_write_env.prepare_buf) {
free(prepare_write_env.prepare_buf);
prepare_write_env.prepare_buf = NULL;
prepare_write_env.prepare_len = 0;
}
return ESP_FAIL;
}
return ESP_OK;
}
static void transport_simple_ble_write(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
uint8_t *outbuf = NULL;
ssize_t outlen = 0;
esp_err_t ret;
ESP_LOGD(TAG, "Inside write with session - %d on attr handle - %d \nLen -%d IS Prep - %d",
param->write.conn_id, param->write.handle, param->write.len, param->write.is_prep);
if (param->write.is_prep) {
ret = prepare_write_event_env(gatts_if, param);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Error appending to prepare buffer");
}
return;
} else {
ESP_LOGD(TAG, "is_prep not set");
}
ret = protocomm_req_handle(protoble_internal->pc_ble,
handle_to_handler(param->write.handle),
param->exec_write.conn_id,
param->write.value,
param->write.len,
&outbuf, &outlen);
if (ret == ESP_OK) {
ret = esp_ble_gatts_set_attr_value(param->write.handle, outlen, outbuf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to set the session attribute value");
}
ret = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
param->write.trans_id, ESP_GATT_OK, NULL);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Send response error in write");
}
hexdump("Response from write", outbuf, outlen);
} else {
ESP_LOGE(TAG, "Invalid content received, killing connection");
esp_ble_gatts_close(gatts_if, param->write.conn_id);
}
if (outbuf) {
free(outbuf);
}
}
static void transport_simple_ble_exec_write(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
esp_err_t err;
uint8_t *outbuf = NULL;
ssize_t outlen = 0;
ESP_LOGD(TAG, "Inside exec_write w/ session - %d", param->exec_write.conn_id);
if ((param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC)
&&
prepare_write_env.prepare_buf) {
err = protocomm_req_handle(protoble_internal->pc_ble,
handle_to_handler(prepare_write_env.handle),
param->exec_write.conn_id,
prepare_write_env.prepare_buf,
prepare_write_env.prepare_len,
&outbuf, &outlen);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Invalid content received, killing connection");
esp_ble_gatts_close(gatts_if, param->write.conn_id);
} else {
hexdump("Response from exec write", outbuf, outlen);
esp_ble_gatts_set_attr_value(prepare_write_env.handle, outlen, outbuf);
}
}
if (prepare_write_env.prepare_buf) {
free(prepare_write_env.prepare_buf);
prepare_write_env.prepare_buf = NULL;
prepare_write_env.prepare_len = 0;
}
err = esp_ble_gatts_send_response(gatts_if, param->exec_write.conn_id, param->exec_write.trans_id, ESP_GATT_OK, NULL);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Send response error in exec write");
}
if (outbuf) {
free(outbuf);
}
}
static void transport_simple_ble_disconnect(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
esp_err_t ret;
ESP_LOGV(TAG, "Inside disconnect w/ session - %d", param->disconnect.conn_id);
if (protoble_internal->pc_ble->sec &&
protoble_internal->pc_ble->sec->close_transport_session) {
ret = protoble_internal->pc_ble->sec->close_transport_session(param->disconnect.conn_id);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "error closing the session after disconnect");
}
}
protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE;
}
static void transport_simple_ble_connect(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
esp_err_t ret;
ESP_LOGV(TAG, "Inside BLE connect w/ conn_id - %d", param->connect.conn_id);
if (protoble_internal->pc_ble->sec &&
protoble_internal->pc_ble->sec->new_transport_session) {
ret = protoble_internal->pc_ble->sec->new_transport_session(param->connect.conn_id);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "error creating the session");
}
}
}
static void transport_simple_ble_set_mtu(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
protoble_internal->gatt_mtu = param->mtu.mtu;
return;
}
static esp_err_t protocomm_ble_add_endpoint(const char *ep_name,
protocomm_req_handler_t req_handler,
void *priv_data)
{
/* Endpoint UUID already added when protocomm_ble_start() was called */
return ESP_OK;
}
static esp_err_t protocomm_ble_remove_endpoint(const char *ep_name)
{
/* Endpoint UUID will be removed when protocomm_ble_stop() is called */
return ESP_OK;
}
static ssize_t populate_gatt_db(esp_gatts_attr_db_t **gatt_db_generated)
{
int i;
/* We need esp_gatts_attr_db_t of size 2 * number of handlers + 1 for service */
ssize_t gatt_db_generated_entries = 2 * protoble_internal->g_nu_lookup_count + 1;
*gatt_db_generated = (esp_gatts_attr_db_t *) malloc(sizeof(esp_gatts_attr_db_t) *
(gatt_db_generated_entries));
if ((*gatt_db_generated) == NULL) {
ESP_LOGE(TAG, "Failed to assign memory to gatt_db");
return -1;
}
/* Declare service */
(*gatt_db_generated)[0].attr_control.auto_rsp = ESP_GATT_RSP_BY_APP;
(*gatt_db_generated)[0].att_desc.uuid_length = ESP_UUID_LEN_16;
(*gatt_db_generated)[0].att_desc.uuid_p = (uint8_t *) &primary_service_uuid;
(*gatt_db_generated)[0].att_desc.perm = ESP_GATT_PERM_READ;
(*gatt_db_generated)[0].att_desc.max_length = sizeof(uint16_t);
(*gatt_db_generated)[0].att_desc.length = sizeof(GATTS_SERVICE_UUID_PROV);
(*gatt_db_generated)[0].att_desc.value = (uint8_t *) &GATTS_SERVICE_UUID_PROV;
/* Declare characteristics */
for (i = 1 ; i < gatt_db_generated_entries ; i++) {
(*gatt_db_generated)[i].attr_control.auto_rsp = ESP_GATT_RSP_BY_APP;
(*gatt_db_generated)[i].att_desc.uuid_length = ESP_UUID_LEN_16;
(*gatt_db_generated)[i].att_desc.perm = ESP_GATT_PERM_READ |
ESP_GATT_PERM_WRITE;
if (i % 2 == 1) { /* Char Declaration */
(*gatt_db_generated)[i].att_desc.uuid_p = (uint8_t *) &character_declaration_uuid;
(*gatt_db_generated)[i].att_desc.max_length = sizeof(uint8_t);
(*gatt_db_generated)[i].att_desc.length = sizeof(uint8_t);
(*gatt_db_generated)[i].att_desc.value = (uint8_t *) &char_prop_read_write;
} else { /* Char Value */
(*gatt_db_generated)[i].att_desc.uuid_p = (uint8_t *)&protoble_internal->g_nu_lookup[i / 2 - 1].uuid;
(*gatt_db_generated)[i].att_desc.max_length = CHAR_VAL_LEN_MAX;
(*gatt_db_generated)[i].att_desc.length = 0;
(*gatt_db_generated)[i].att_desc.value = NULL;
}
}
return gatt_db_generated_entries;
}
static void protocomm_ble_cleanup(void)
{
if (protoble_internal) {
if (protoble_internal->g_nu_lookup) {
for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) {
if (protoble_internal->g_nu_lookup[i].name) {
free(protoble_internal->g_nu_lookup[i].name);
}
}
free(protoble_internal->g_nu_lookup);
}
free(protoble_internal);
protoble_internal = NULL;
}
if (protocomm_ble_device_name) {
free(protocomm_ble_device_name);
protocomm_ble_device_name = NULL;
}
if (adv_data.p_service_uuid) {
free(adv_data.p_service_uuid);
adv_data.p_service_uuid = NULL;
adv_data.service_uuid_len = 0;
}
}
esp_err_t protocomm_ble_start(protocomm_t *pc, const protocomm_ble_config_t *config)
{
if (!pc || !config || !config->device_name || !config->nu_lookup) {
return ESP_ERR_INVALID_ARG;
}
if (protoble_internal) {
ESP_LOGE(TAG, "Protocomm BLE already started");
return ESP_FAIL;
}
/* Store service UUID internally */
adv_data.service_uuid_len = sizeof(config->service_uuid);
adv_data.p_service_uuid = malloc(sizeof(config->service_uuid));
if (adv_data.p_service_uuid == NULL) {
ESP_LOGE(TAG, "Error allocating memory for storing service UUID");
protocomm_ble_cleanup();
return ESP_ERR_NO_MEM;
}
memcpy(adv_data.p_service_uuid, config->service_uuid, adv_data.service_uuid_len);
/* Store BLE device name internally */
protocomm_ble_device_name = strdup(config->device_name);
if (protocomm_ble_device_name == NULL) {
ESP_LOGE(TAG, "Error allocating memory for storing BLE device name");
protocomm_ble_cleanup();
return ESP_ERR_NO_MEM;
}
protoble_internal = (_protocomm_ble_internal_t *) calloc(1, sizeof(_protocomm_ble_internal_t));
if (protoble_internal == NULL) {
ESP_LOGE(TAG, "Error allocating internal protocomm structure");
protocomm_ble_cleanup();
return ESP_ERR_NO_MEM;
}
protoble_internal->g_nu_lookup_count = config->nu_lookup_count;
protoble_internal->g_nu_lookup = malloc(config->nu_lookup_count * sizeof(protocomm_ble_name_uuid_t));
if (protoble_internal->g_nu_lookup == NULL) {
ESP_LOGE(TAG, "Error allocating internal name UUID table");
protocomm_ble_cleanup();
return ESP_ERR_NO_MEM;
}
for (unsigned i = 0; i < protoble_internal->g_nu_lookup_count; i++) {
protoble_internal->g_nu_lookup[i].uuid = config->nu_lookup[i].uuid;
protoble_internal->g_nu_lookup[i].name = strdup(config->nu_lookup[i].name);
if (protoble_internal->g_nu_lookup[i].name == NULL) {
ESP_LOGE(TAG, "Error allocating internal name UUID entry");
protocomm_ble_cleanup();
return ESP_ERR_NO_MEM;
}
}
pc->add_endpoint = protocomm_ble_add_endpoint;
pc->remove_endpoint = protocomm_ble_remove_endpoint;
protoble_internal->pc_ble = pc;
protoble_internal->gatt_mtu = ESP_GATT_DEF_BLE_MTU_SIZE;
simple_ble_cfg_t *ble_config = simple_ble_init();
if (ble_config == NULL) {
ESP_LOGE(TAG, "Ran out of memory for BLE config");
protocomm_ble_cleanup();
return ESP_ERR_NO_MEM;
}
/* Set function pointers required for simple BLE layer */
ble_config->read_fn = transport_simple_ble_read;
ble_config->write_fn = transport_simple_ble_write;
ble_config->exec_write_fn = transport_simple_ble_exec_write;
ble_config->disconnect_fn = transport_simple_ble_disconnect;
ble_config->connect_fn = transport_simple_ble_connect;
ble_config->set_mtu_fn = transport_simple_ble_set_mtu;
/* Set parameters required for advertising */
ble_config->adv_data = adv_data;
ble_config->adv_params = adv_params;
ble_config->device_name = protocomm_ble_device_name;
ble_config->gatt_db_count = populate_gatt_db(&ble_config->gatt_db);
if (ble_config->gatt_db_count == -1) {
ESP_LOGE(TAG, "Invalid GATT database count");
simple_ble_deinit();
protocomm_ble_cleanup();
return ESP_ERR_INVALID_STATE;
}
esp_err_t err = simple_ble_start(ble_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "simple_ble_start failed w/ error code 0x%x", err);
simple_ble_deinit();
protocomm_ble_cleanup();
return err;
}
prepare_write_env.prepare_buf = NULL;
ESP_LOGV(TAG, "Waiting for client to connect ......");
return ESP_OK;
}
esp_err_t protocomm_ble_stop(protocomm_t *pc)
{
if ((pc != NULL) &&
(protoble_internal != NULL ) &&
(pc == protoble_internal->pc_ble)) {
esp_err_t ret = ESP_OK;
ret = simple_ble_stop();
if (ret) {
ESP_LOGE(TAG, "BLE stop failed");
}
simple_ble_deinit();
protocomm_ble_cleanup();
return ret;
}
return ESP_ERR_INVALID_ARG;
}

View File

@ -0,0 +1,224 @@
// Copyright 2018 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.
#include <stdio.h>
#include <string.h>
#include <esp_log.h>
#include <esp_console.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <driver/uart.h>
#include <protocomm.h>
#include <protocomm_console.h>
#include "protocomm_priv.h"
static const char *TAG = "protocomm_console";
static uint32_t session_id = PROTOCOMM_NO_SESSION_ID;
static protocomm_t *pc_console = NULL; /* The global protocomm instance for console */
static TaskHandle_t console_task = NULL;
esp_err_t protocomm_console_stop(protocomm_t *pc)
{
if (pc != pc_console) {
ESP_LOGE(TAG, "Incorrect stop request");
return ESP_ERR_INVALID_ARG;
}
ESP_LOGI(TAG, "Stopping console...");
xTaskNotifyGive(console_task);
return ESP_OK;
}
static ssize_t hex2bin(const char *hexstr, uint8_t *bytes)
{
size_t hexstrLen = strlen(hexstr);
ssize_t bytesLen = hexstrLen / 2;
int count = 0;
const char *pos = hexstr;
for(count = 0; count < bytesLen; count++) {
sscanf(pos, "%2hhx", &bytes[count]);
pos += 2;
}
return bytesLen;
}
static bool stopped(void)
{
uint32_t flag = 0;
xTaskNotifyWait(0, 0, &flag, (TickType_t) 10/portTICK_RATE_MS);
return (flag != 0);
}
static void protocomm_console_task(void *arg)
{
int uart_num = (int) arg;
uint8_t linebuf[256];
int i, cmd_ret;
esp_err_t ret;
QueueHandle_t uart_queue;
uart_event_t event;
ESP_LOGV(TAG, "Initialising UART on port %d", uart_num);
uart_driver_install(uart_num, 256, 0, 8, &uart_queue, 0);
/* Initialize the console */
esp_console_config_t console_config = {
.max_cmdline_args = 8,
.max_cmdline_length = 256,
};
esp_console_init(&console_config);
esp_console_register_help_command();
while (!stopped()) {
uart_write_bytes(uart_num, "\n>> ", 4);
memset(linebuf, 0, sizeof(linebuf));
i = 0;
do {
ret = xQueueReceive(uart_queue, (void * )&event, (TickType_t) 10/portTICK_RATE_MS);
if (ret != pdPASS) {
if (stopped()) {
break;
} else {
continue;
}
}
if (event.type == UART_DATA) {
while (uart_read_bytes(uart_num, (uint8_t *) &linebuf[i], 1, 0)) {
if (linebuf[i] == '\r') {
uart_write_bytes(uart_num, "\r\n", 2);
} else {
uart_write_bytes(uart_num, (char *) &linebuf[i], 1);
}
i++;
}
}
} while ((i < 255) && linebuf[i-1] != '\r');
if (stopped()) {
break;
}
ret = esp_console_run((char *) linebuf, &cmd_ret);
if (ret < 0) {
ESP_LOGE(TAG, "Console dispatcher error\n");
break;
}
}
if (pc_console->sec && pc_console->sec->cleanup) {
pc_console->sec->cleanup();
}
pc_console = NULL;
esp_console_deinit();
ESP_LOGI(TAG, "Console stopped");
vTaskDelete(NULL);
}
static int common_cmd_handler(int argc, char** argv)
{
int i, ret;
uint32_t cur_session_id = atoi(argv[1]);
uint8_t *buf = (uint8_t *) malloc(strlen(argv[2]));
uint8_t *outbuf;
ssize_t outlen;
ssize_t len = hex2bin(argv[2], buf);
if (cur_session_id != session_id) {
if (pc_console->sec && pc_console->sec->new_transport_session) {
ret = pc_console->sec->new_transport_session(cur_session_id);
if (ret == ESP_OK) {
session_id = cur_session_id;
}
}
}
ret = protocomm_req_handle(pc_console, argv[0], cur_session_id, buf, len, &outbuf, &outlen);
free(buf);
if (ret == ESP_OK) {
printf("\r\n");
for (i = 0; i < outlen; i++) {
printf("%02x", outbuf[i]);
}
printf("\r\n");
/* Transport is responsible for freeing the transmit buffer */
free(outbuf);
return ESP_OK;
} else {
return ret;
}
}
static esp_err_t protocomm_console_add_endpoint(const char *ep_name, protocomm_req_handler_t req_handler, void *priv_data)
{
(void) req_handler;
(void) priv_data;
esp_err_t ret;
esp_console_cmd_t cmd;
memset(&cmd, 0, sizeof(cmd));
cmd.command = ep_name;
cmd.help = "";
cmd.func = common_cmd_handler;
ret = esp_console_cmd_register(&cmd);
return ret;
}
static esp_err_t protocomm_console_remove_endpoint(const char *ep_name)
{
/* Command deletion happens internally in esp_console_deinit function */
return ESP_OK;
}
esp_err_t protocomm_console_start(protocomm_t *pc, const protocomm_console_config_t *config)
{
if (pc == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (pc_console != NULL) {
if (pc_console == pc) {
return ESP_ERR_INVALID_STATE;
}
else {
return ESP_ERR_NOT_SUPPORTED;
}
}
if (xTaskCreate(protocomm_console_task, "protocomm_console",
config->stack_size, NULL, config->task_priority, &console_task) != pdPASS) {
return ESP_FAIL;
}
pc->add_endpoint = protocomm_console_add_endpoint;
pc->remove_endpoint = protocomm_console_remove_endpoint;
pc_console = pc;
return ESP_OK;
}

View File

@ -0,0 +1,249 @@
// Copyright 2018 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.
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
#include <esp_err.h>
#include <http_server.h>
#include <protocomm.h>
#include <protocomm_httpd.h>
#include "protocomm_priv.h"
static const char *TAG = "protocomm_httpd";
static protocomm_t *pc_httpd; /* The global protocomm instance for HTTPD */
static uint32_t session_id = PROTOCOMM_NO_SESSION_ID;
#define MAX_REQ_BODY_LEN 4096
static esp_err_t common_post_handler(httpd_req_t *req)
{
esp_err_t ret;
uint8_t *outbuf = NULL;
char *req_body = NULL;
const char *ep_name = NULL;
ssize_t outlen;
int cur_session_id = httpd_req_to_sockfd(req);
if (cur_session_id != session_id) {
/* Initialise new security session */
if (session_id != PROTOCOMM_NO_SESSION_ID) {
ESP_LOGV(TAG, "Closing session with ID: %d", session_id);
/* Presently HTTP server doesn't support callback on socket closure so
* previous session can only be closed when new session is requested */
if (pc_httpd->sec && pc_httpd->sec->close_transport_session) {
ret = pc_httpd->sec->close_transport_session(session_id);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to close session with ID: %d", session_id);
ret = ESP_FAIL;
goto out;
}
}
session_id = PROTOCOMM_NO_SESSION_ID;
}
if (pc_httpd->sec && pc_httpd->sec->new_transport_session) {
ret = pc_httpd->sec->new_transport_session(cur_session_id);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to launch new session with ID: %d", cur_session_id);
ret = ESP_FAIL;
goto out;
}
}
session_id = cur_session_id;
ESP_LOGV(TAG, "New session with ID: %d", cur_session_id);
}
if (req->content_len <= 0) {
ESP_LOGE(TAG, "Content length not found");
ret = ESP_FAIL;
goto out;
} else if (req->content_len > MAX_REQ_BODY_LEN) {
ESP_LOGE(TAG, "Request content length should be less than 4kb");
ret = ESP_FAIL;
goto out;
}
req_body = (char *) malloc(req->content_len);
if (!req_body) {
ESP_LOGE(TAG, "Unable to allocate for request length %d", req->content_len);
ret = ESP_ERR_NO_MEM;
goto out;
}
size_t recv_size = 0;
while (recv_size < req->content_len) {
ret = httpd_req_recv(req, req_body + recv_size, req->content_len - recv_size);
if (ret < 0) {
ret = ESP_FAIL;
goto out;
}
recv_size += ret;
}
/* Extract the endpoint name from URI string of type "/ep_name" */
ep_name = req->uri + 1;
ret = protocomm_req_handle(pc_httpd, ep_name, session_id,
(uint8_t *)req_body, recv_size, &outbuf, &outlen);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Data handler failed");
ret = ESP_FAIL;
goto out;
}
ret = httpd_resp_send(req, (char *)outbuf, outlen);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "HTTP send failed");
ret = ESP_FAIL;
goto out;
}
ret = ESP_OK;
out:
if (req_body) {
free(req_body);
}
if (outbuf) {
free(outbuf);
}
return ret;
}
esp_err_t protocomm_httpd_add_endpoint(const char *ep_name,
protocomm_req_handler_t req_handler,
void *priv_data)
{
if (pc_httpd == NULL) {
return ESP_ERR_INVALID_STATE;
}
ESP_LOGV(TAG, "Adding endpoint : %s", ep_name);
/* Construct URI name by prepending '/' to ep_name */
char* ep_uri = calloc(1, strlen(ep_name) + 2);
if (!ep_uri) {
ESP_LOGE(TAG, "Malloc failed for ep uri");
return ESP_ERR_NO_MEM;
}
/* Create URI handler structure */
sprintf(ep_uri, "/%s", ep_name);
httpd_uri_t config_handler = {
.uri = ep_uri,
.method = HTTP_POST,
.handler = common_post_handler,
.user_ctx = NULL
};
/* Register URI handler */
esp_err_t err;
httpd_handle_t *server = (httpd_handle_t *) pc_httpd->priv;
if ((err = httpd_register_uri_handler(*server, &config_handler)) != ESP_OK) {
ESP_LOGE(TAG, "Uri handler register failed: %s", esp_err_to_name(err));
free(ep_uri);
return ESP_FAIL;
}
free(ep_uri);
return ESP_OK;
}
static esp_err_t protocomm_httpd_remove_endpoint(const char *ep_name)
{
if (pc_httpd == NULL) {
return ESP_ERR_INVALID_STATE;
}
ESP_LOGV(TAG, "Removing endpoint : %s", ep_name);
/* Construct URI name by prepending '/' to ep_name */
char* ep_uri = calloc(1, strlen(ep_name) + 2);
if (!ep_uri) {
ESP_LOGE(TAG, "Malloc failed for ep uri");
return ESP_ERR_NO_MEM;
}
sprintf(ep_uri, "/%s", ep_name);
/* Unregister URI handler */
esp_err_t err;
httpd_handle_t *server = (httpd_handle_t *) pc_httpd->priv;
if ((err = httpd_unregister_uri_handler(*server, ep_uri, HTTP_POST)) != ESP_OK) {
ESP_LOGE(TAG, "Uri handler de-register failed: %s", esp_err_to_name(err));
free(ep_uri);
return ESP_FAIL;
}
free(ep_uri);
return ESP_OK;
}
esp_err_t protocomm_httpd_start(protocomm_t *pc, const protocomm_httpd_config_t *config)
{
if (!pc || !config) {
return ESP_ERR_INVALID_ARG;
}
if (pc_httpd) {
if (pc == pc_httpd) {
ESP_LOGE(TAG, "HTTP server already running for this protocomm instance");
return ESP_ERR_INVALID_STATE;
}
ESP_LOGE(TAG, "HTTP server started for another protocomm instance");
return ESP_ERR_NOT_SUPPORTED;
}
/* Private data will point to the HTTP server handle */
pc->priv = calloc(1, sizeof(httpd_handle_t));
if (!pc->priv) {
ESP_LOGE(TAG, "Malloc failed for HTTP server handle");
return ESP_ERR_NO_MEM;
}
/* Configure the HTTP server */
httpd_config_t server_config = HTTPD_DEFAULT_CONFIG();
server_config.server_port = config->port;
server_config.stack_size = config->stack_size;
server_config.task_priority = config->task_priority;
server_config.lru_purge_enable = true;
server_config.max_open_sockets = 1;
esp_err_t err;
if ((err = httpd_start((httpd_handle_t *)pc->priv, &server_config)) != ESP_OK) {
ESP_LOGE(TAG, "Failed to start http server: %s", esp_err_to_name(err));
free(pc->priv);
return err;
}
pc->add_endpoint = protocomm_httpd_add_endpoint;
pc->remove_endpoint = protocomm_httpd_remove_endpoint;
pc_httpd = pc;
return ESP_OK;
}
esp_err_t protocomm_httpd_stop(protocomm_t *pc)
{
if ((pc != NULL) && (pc == pc_httpd)) {
httpd_handle_t *server_handle = (httpd_handle_t *) pc_httpd->priv;
httpd_stop(*server_handle);
free(server_handle);
pc_httpd->priv = NULL;
pc_httpd = NULL;
return ESP_OK;
}
return ESP_ERR_INVALID_ARG;
}