络达开发---自定义BLE服务(二):功能实现

络达开发--自定义BLE服务(一)

一、目录和工程的配置

本文讲解如何在该SDK中添加用户自居定义的BLE服务。该服务的源码可以存放在自己希望的位置,但为符合工程目录的合理性,建议放在工程所在的目录下面,假设我们的工程名称为: headset_ghp

那么,笔者的BLE自定义服务源码存放的路径是:

bta_sdk\mcu\project\ab1565_ab1568_evk\apps\headset_ghp\ble_ghp\

笔者自定义的服务目录如下图所示,创建时参考了:bta_sdk\mcu\middleware\MTK\bt_air\src\ble_air目录下的服务:

  • 源码目录创建好后,需要把该源码加入到工程中:把目录中的module.mk添加到工程的Makefile中(路径:
bta_sdk\mcu\project\ab1565_ab1568_evk\apps\headset_ghp\GCC\Makefile);
  • 添加内容如下:
#######################################################

# Main APP files

APP_PATH        := $(patsubst $(SDK_PATH)/%,%,$(abspath $(dir $(PWD))))

APP_PATH_SRC    := $(APP_PATH)/src


include $(SOURCE_DIR)/$(APP_PATH_SRC)/apps/module.mk

include $(SOURCE_DIR)/$(APP_PATH)/ble_ghp/module.mk

注意:添加的行需要在APP_PATH 定义的后面,否则会module.mk中所使用的变量APP_PATH会为空值导至找不到源码文件;

  • module.mk文件内容:
    BT_GHP_PATH = $(SOURCE_DIR)/$(APP_PATH)/ble_ghp/src
    
    C_FILES  += $(BT_GHP_PATH)/ble_ghp_service.c
    #################################################################################
    #include path
    #################################################################################
    CFLAGS  += -I$(SOURCE_DIR)/$(APP_PATH)/ble_ghp/inc
    CFLAGS  += -I$(SOURCE_DIR)/kernel/rtos/FreeRTOS/Source/include 
    CFLAGS  += -I$(SOURCE_DIR)/kernel/rtos/FreeRTOS/Source/portable/GCC/ARM_CM4F
    CFLAGS  += -I$(SOURCE_DIR)/driver/chip/inc
    CFLAGS  += -I$(SOURCE_DIR)/kernel/service/inc 
    CFLAGS  += -I$(SOURCE_DIR)/middleware/MTK/bt_callback_manager/inc
    CFLAGS  += -I$(SOURCE_DIR)/middleware/MTK/nvdm/inc
    
    ifeq ($(MTK_AWS_MCE_ENABLE),y)
    ifeq ($(SUPPORT_ROLE_HANDOVER_SERVICE),y)
    CFLAGS  += -I$(SOURCE_DIR)/middleware/MTK/bluetooth_service/bt_role_handover_service/inc
    endif
    endif
    
    ifeq ($(MTK_CUS_FEATURE_BLE_GHP_ENABLE),y)
    CFLAGS += -DMTK_CUS_FEATURE_BLE_GHP_ENABLE
    endif

    从上面可以看出,笔者使用了一个宏定义来开或关该服务:MTK_CUS_FEATURE_BLE_GHP_ENABLE

 二、服务的实现

自定义的BLE服务需要使用128位的uuid,UUID的值用户可以自行定义;同时,服务中的属性可以支持不同的操作权限,如可读/可写/可订阅等,或者是需要鉴权后才可以操作属性,这些权限的定义在头文件:bt_gatt.h中均有定义,如

BT_GATT_CHARC_PROP_READ/BT_GATT_CHARC_PROP_NOTIFY/BT_GATT_CHARC_PROP_WRITE

最常使用的属性的操作有三个:读/写/订阅;这三个权限再实例化属性描述符时就需要进行声明,并且对读和写的操作,还需要有相应的回调函数或者是对值的描述符的定义。此处笔者采用了通过回调函数来支持读和写及订阅的操作。

笔者的UUID的及各描述符的定义如下所示:

//此处定义一个日志类型,这样在调试看日志的时候,就可以以此为过滤条件来仅显示该类型的日志;

log_create_module(GHP, PRINT_LEVEL_INFO);
#define ble_ghp_hex_dump(_message,...)   LOG_HEXDUMP_I(GHP, (_message), ##__VA_ARGS__)

//下面是主服务的UUID及该服务下两个属性的UUID的定义,注意观察这些值,其中大部分的值都是一样的,仅有2个字节用来区分不同的属性,如粗体数值所示。

#define GHP_SERVICE_UUID                \
    {{0x45, 0x4C, 0x42, 0x61, 0x68, 0x6F, 0x72, 0x69, \
      0x41, 0x03, 0xAB, 0x2D, 0x4D, 0x49, 0x19, 0x73}}

#define GHP_RX_CHAR_UUID                \
    {{0x45, 0x4C, 0x42, 0x61, 0x68, 0x6F, 0x72, 0x69, \
      0x41, 0x32, 0xAB, 0x2D, 0x52, 0x41, 0x19, 0x73}}

#define GHP_TX_CHAR_UUID                \
    {{0x45, 0x4C, 0x42, 0x61, 0x68, 0x6F, 0x72, 0x69, \
      0x41, 0x31, 0xAB, 0x2D, 0x52, 0x41, 0x19, 0x73}}


如下代码,还需要定义两个属性的句柄,在描述符实例化的时候需要使用。

const bt_uuid_t GHP_RX_CHAR_UUID128 = GHP_RX_CHAR_UUID;
const bt_uuid_t GHP_TX_CHAR_UUID128 = GHP_TX_CHAR_UUID;
//主服务对象的定义
BT_GATTS_NEW_PRIMARY_SERVICE_128(ble_ghp_primary_service, GHP_SERVICE_UUID);

//第一个属性的定义,可以发现,该属性支持开放式的读+写+订阅,
BT_GATTS_NEW_CHARC_128(ble_ghp_rx_char, BT_GATT_CHARC_PROP_WRITE_WITHOUT_RSP | 
                                        BT_GATT_CHARC_PROP_WRITE | 
                                        BT_GATT_CHARC_PROP_READ |
                                        BT_GATT_CHARC_PROP_NOTIFY, GHP_RX_CHAR_VALUE_HANDLE, GHP_RX_CHAR_UUID);

//下面这个是对该属性的值对象的定义,定义了该值是可读和可写,这个需要和属性对象的定义对应起来,因为属性定义中支持读和写。并且还需需要添加一个回调函数,当客户端对该值进行读或写时,就会触发该回调函数,在该函数中来实现对应值的读和写操作。
BT_GATTS_NEW_CHARC_VALUE_CALLBACK(ble_ghp_rx_char_value, GHP_RX_CHAR_UUID128, 
                                    BT_GATTS_REC_PERM_WRITABLE |
                                    BT_GATTS_REC_PERM_READABLE, ble_ghp_rx_char_callback);
//下面这行是对该属性起了一个易读的名字,通常是个字符串,该实例中采用了定长字符串来显示,当然,也可以使用动态的方式,这个就需要使用宏:BT_GATTS_NEW_CHARC_USER_DESCRIPTION来实现,因为它支持回调函数,可以在回调函数中来动态更改该易读的名字。
BT_GATTS_NEW_CHARC_USER_DESCRIPTION_STR16(ble_ghp_rx_desc, BT_GATTS_REC_PERM_READABLE, 3,"Rx");

//因为该属性同时还支持订阅,因此还需要添加一个cccd描述符对象,用来给客户端提供订阅的接口。客户端点击订阅或取消都会触发该宏定义中的回调函数,其值决定了是否打开订阅功能(1=打开,0=取消订阅)。所以,该描述符需要支持读和写的权限。

BT_GATTS_NEW_CLIENT_CHARC_CONFIG(ble_ghp_rx_client_config, BT_GATTS_REC_PERM_READABLE|BT_GATTS_REC_PERM_WRITABLE, ble_ghp_rx_char_cccd_callback);

//下面是定义了第二个属性,其原更同第一个类似,这里不再重复解释。其中一个区别是该属性的值不支持写操作,且默认值为123;

BT_GATTS_NEW_CHARC_128(ble_ghp_tx_char, BT_GATT_CHARC_PROP_NOTIFY, GHP_TX_CHAR_VALUE_HANDLE, GHP_TX_CHAR_UUID);
BT_GATTS_NEW_CHARC_VALUE_UINT8(ble_ghp_tx_char_value, GHP_TX_CHAR_UUID128, BT_GATTS_REC_PERM_READABLE, 123);
BT_GATTS_NEW_CHARC_USER_DESCRIPTION_STR16(ble_ghp_tx_desc, BT_GATTS_REC_PERM_READABLE, 3,"Tx");
BT_GATTS_NEW_CLIENT_CHARC_CONFIG(ble_ghp_tx_client_config, BT_GATTS_REC_PERM_READABLE|BT_GATTS_REC_PERM_WRITABLE, ble_ghp_tx_char_cccd_callback);

上面对各服务的属性的对象定义之后,还需要把这些对象有序的组织起来,如下代码所示,注意,这里顺序是有要求的:

static const bt_gatts_service_rec_t *ble_ghp_service_rec[] = {
    (const bt_gatts_service_rec_t*) &ble_ghp_primary_service, //starting_handle=0xA401

    (const bt_gatts_service_rec_t*) &ble_ghp_rx_char,         //0xA402
    (const bt_gatts_service_rec_t*) &ble_ghp_rx_char_value,   //0xA403
    (const bt_gatts_service_rec_t*) &ble_ghp_rx_desc,         //0xA404
    (const bt_gatts_service_rec_t*) &ble_ghp_rx_client_config,//0xA405
    
    (const bt_gatts_service_rec_t*) &ble_ghp_tx_char,         //0xA406
    (const bt_gatts_service_rec_t*) &ble_ghp_tx_char_value,   //0xA407
    (const bt_gatts_service_rec_t*) &ble_ghp_tx_desc,         //0xA408
    (const bt_gatts_service_rec_t*) &ble_ghp_tx_client_config,//ending_handle=0xA409
};

下面这个定义,用来声明该服务下面所有描述符对象的句柄的范围,值为uint16_t类型,如下代码中的值0xA401和0xA409;

那么,这两个值的定义有什么要求呢?我们看上面的代码ble_ghp_service_rec[]的定义中的注释,可以发现其数据的长度刚好是starting_handle和ending_handle的差值的长度,且句柄的起始值从starting_handle开始。

const bt_gatts_service_t ble_ghp_service = {
    .starting_handle = 0xA401,
    .ending_handle   = 0xA409,
    .required_encryption_key_size = 0,
    .records = ble_ghp_service_rec
};

那么另一个问题,为什么是0xA401开始?这个就和SDK中其它服务句柄的定义有关系了。我们可以查看如下路径中的文件:

bta_sdk\mcu\project\ab1565_ab1568_evk\apps\headset_ref_design\src\bt_app_utility\gatt_service.c

找到如下代码:

/**< gatt server collects all service. */
const bt_gatts_service_t *bt_if_clm_gatt_server[] = {
    &bt_if_gap_service, /**< handle range: 0x0001 to 0x0009. */
    &bt_if_gatt_service,/**< handle range: 0x0011 to 0x0015. */
#ifdef MTK_BLE_SMTCN_ENABLE
    &bt_if_dtp_service, //0x0014-0x0017
#endif
    /*&bt_if_dogp_service,*/ /**< handle range: 0x0020 to 0x0025. */
#ifdef __BLE_BAS__
    &ble_bas_service,/**< handle range: 0x0031 to 0x0034. */
#endif
#ifdef MTK_BLE_IAS
    &ble_ias_service,/**< handle range: 0x0040 to 0x0042. */
#endif
#ifdef MTK_PORT_SERVICE_BT_ENABLE
    &ble_air_service,/**< handle range: 0x0051 to 0x0056. */
#endif
    &ble_dis_service,/**< handle range: 0x0060 to 0x0072. */
#ifdef MTK_AMA_ENABLE
    &ble_ama_service,/**handle range: 0x00D0 to 0x00D5. */
#endif
#ifdef MTK_VA_XIAOWEI_ENABLE
    &xiaowei_ble_service,
#endif
#ifdef MTK_VA_XIAOAI_ENABLE
    &xiaoai_ble_service,            /**< handle range: 0x00F0 to 0x00F5. */
#endif
#ifdef __BT_FAST_PAIR_ENABLE__
    &bt_fast_pair_service,/**< handle range: 0x0100 to 0x013F. */
#endif
#ifdef AIR_LE_AUDIO_ENABLE
    &ble_ascs_service,   /**< handle range: 0x1103 to 0x110F. */
    &ble_pacs_service,   /**< handle range: 0x1200 to 0x1212. */
    &ble_vcs_service,    /**< handle range: 0x1301 to 0x1309. */
    &ble_vocs_service_channel_1, /**< handle range: 0x2001 to 0x200C. */
#ifdef AIR_LE_AUDIO_HEADSET_ENABLE
    &ble_vocs_service_channel_2, /**< handle range: 0x3001 to 0x300C. */
#endif
    &ble_aics_service,   /**< handle range: 0x4001 to 0x4010. */
    &ble_mics_service,   /**< handle range: 0x5001 to 0x5004. */
    &ble_csis_service,   /**< handle range: 0x6001 to 0x600A. */
    &ble_cas_service,    /**< handle range: 0x7001 to 0x7002. */
    &ble_bass_service,   /**< handle range: 0xA201 to 0xA209. */
    &ble_tmas_service,   /**< handle range: 0xA301 to 0xA303. */
#endif
#ifdef MTK_CUS_FEATURE_BLE_GHP_ENABLE 
    &ble_ghp_service,    /**< handle range: 0xA401 to 0xA409. */
#endif
    NULL
};

从这段代码的注释中我们可以发现,每个Primary services都会占用若干个句柄的值,且大至是按顺序去使用0x0000~0xFFFF这段的值,因为SDK中已经使用到了0xA303,因此笔者继续从0xA401值开始定义。当然,你也可以从0xA304开始,只要不和系统中已经被占用的句柄的值有冲突就基本是没问题的,但还是建议按照顺序的规则来使用,这样不容易乱。

再看属性值的读和写操作,回调函数如下所示:

static uint32_t ble_ghp_rx_char_cccd_callback(const uint8_t rw, uint16_t handle, void *data, uint16_t size, uint16_t offset)
{
    uint16_t val=255;

    if(size == 2)  val = *(uint16_t*)data;
    LOG_MSGID_I(GHP, "[BLE_GHP]ble_ghp_rx_char_cccd_callback,hd:%d, rw%d,size%d, offset%d,val%d\r\n", 5, handle, rw, size, offset, val);
    if ((handle != BT_HANDLE_INVALID) && (handle == g_ghp_cntx.conn_handle)) {
        /** record for each connection. */
        if (rw == BT_GATTS_CALLBACK_WRITE) {
            if (size != sizeof(uint16_t)) { //Size check
                return 0;
            }
            g_ghp_cntx.notify_enabled = (val&1)<<1;
            LOG_MSGID_I(GHP, "[BLE_GHP]rx_data:%d \r\n", 1,  g_ghp_cntx.notify_enabled);
        } else if (rw == BT_GATTS_CALLBACK_READ) {
            if (size != 0) {
                uint16_t *buf = (uint16_t*) data;
                *buf = (uint16_t) g_ghp_cntx.notify_enabled;
                LOG_MSGID_I(GHP, "[BLE_GHP]read rx cccd value = %d\r\n", 1, *buf);
                return size;
            }
        }
        return sizeof(uint16_t);
    }
    return 0;
}

其中参数rw即是用来区分对属性是读(BT_GATTS_CALLBACK_READ还是写(BT_GATTS_CALLBACK_WRITE)。注意这里的size的值,在读的时候,若size的值为0,表示系统需要通过该函数的返回值来确认待读数据的长度,因此这时不能往data参数中写数据,仅当size值不为0时才可以向data中写数据。在对属性值读的时候,系统都会首先通过该回调函数来获取待读数据的长度,然后再来读有效的数据。

其中参数offset用来表示向目标数据中写或读的偏移量,因为目标数据可以是一个很大的数据,无法采用普通的如byte,uint16,uint32这样的数据来表示,所以这时候就需要使用offset这个参数来实现对复合数据的读写。

再来看CCCD的回调函数:

static uint32_t ble_ghp_tx_char_cccd_callback(const uint8_t rw, uint16_t handle, void *data, uint16_t size, uint16_t offset)
{
    uint16_t val=255;

    if(size == 2)  val = *(uint16_t*)data;
    LOG_MSGID_I(GHP, "[BLE_GHP]ble_ghp_tx_char_cccd_callback, hd:%d, rw%d,size%d,offset%d,val%d\r\n", 5, handle, rw, size,offset, val);
    if ((handle != BT_HANDLE_INVALID) && (handle == g_ghp_cntx.conn_handle)) {
        /** record for each connection. */
        if (rw == BT_GATTS_CALLBACK_WRITE) {
            if (size != sizeof(uint16_t)) { //Size check
                return 0;
            }
            g_ghp_cntx.notify_enabled = (val&1)<<0;
            LOG_MSGID_I(GHP, "[BLE_GHP]rx_data:%d \r\n", 1,  g_ghp_cntx.notify_enabled);
        } else if (rw == BT_GATTS_CALLBACK_READ) {
            if (size != 0) {
                uint16_t *buf = (uint16_t*) data;
                *buf = (uint16_t) g_ghp_cntx.notify_enabled;
                LOG_MSGID_I(GHP, "[BLE_GHP]read tx cccd value = %d\r\n", 1, *buf);
                return size;
            }
        }
        return sizeof(uint16_t);
    }
    return 0;
}

同属性值的读写的回调函数类似,这里操作的不是属性值,而是一个用户自己用来记录订阅是否开启的变量:g_ghp_cntx.notify_enabled;

那么,对于订阅的功能,笔者采用了一个Timer来周期的通知客户端,Timer的回调函数如下所示,当然,也可以在工程中别的地方通过调用函数:

ble_ghp_write_data来实现对客户端的数据通知。

uint16_t lgTxVal=0;
static void notify_timer_callback(TimerHandle_t expiredTimer)
{
    lgTxVal++;
    LOG_MSGID_I(GHP,"[BLE_GHP]in timer callback function, tx:%d\r\n", 1, lgTxVal);
    
    ble_ghp_write_data(g_ghp_cntx.conn_handle, &lgTxVal, 2);
}


/**
 * @brief Function for sending the Ghp service tx characteristic value.
 *
 * @param[in]   conn_handle                           connection handle.
 *
 * @return      ble_status_t                              0 means success.
 */
static bt_status_t ble_ghp_service_tx_send(bt_handle_t conn_handle, uint8_t *data, uint32_t length)
{
    bt_status_t status = BT_STATUS_FAIL;

    LOG_MSGID_I(GHP, "[BLE_GHP]ble_ghp_service_tx_send! hd:%d,len:%d \r\n", 2, conn_handle, length);
    uint8_t *buf = NULL;
    bt_gattc_charc_value_notification_indication_t *ghp_noti_rsp;
    ble_ghp_cntx_t *buffer_t = ble_ghp_get_cntx_by_handle(conn_handle);

    buf = (uint8_t *)pvPortMalloc(5 + length);
    if (buf == NULL) {
        LOG_MSGID_I(GHP, "[BLE_GHP]ble_ghp_service_tx_send fail, OOM!\r\n", 0);  
        return status;
    }
    ghp_noti_rsp = (bt_gattc_charc_value_notification_indication_t*) buf;
    LOG_MSGID_I(GHP, "[BLE_GHP]ble_ghp_service_tx_send, new RX Char Value is %d\r\n", 1, data[0]);  

    if ((conn_handle != BT_HANDLE_INVALID) && (buffer_t) &&
        (BLE_GHP_CCCD_NOTIFICATION ==  buffer_t->notify_enabled)) {
        ghp_noti_rsp->att_req.opcode = BT_ATT_OPCODE_HANDLE_VALUE_NOTIFICATION;
        ghp_noti_rsp->att_req.handle = GHP_TX_CHAR_VALUE_HANDLE;
        memcpy((void*)(ghp_noti_rsp->att_req.attribute_value), data, length);
        ghp_noti_rsp->attribute_value_length = 3 + length;
        
        LOG_MSGID_I(GHP, "[BLE_GHP]ble_ghp_service_notify conn_handle is %x, send data is %d\r\n", 2, conn_handle, data[0]);
        
        status = bt_gatts_send_charc_value_notification_indication(conn_handle, ghp_noti_rsp); 
    }
    if (buf != NULL) {
        vPortFree(buf);
    }
    return status;
}



/**
 * @brief Function for application to write data to the send buffer.
 */
uint32_t ble_ghp_write_data(uint16_t conn_handle, uint8_t *buffer, uint32_t size)
{
    BLEGHP_MUTEX_LOCK();  
    LOG_MSGID_I(GHP, "[BLE_GHP]ble_ghp_write_data incoming, hd:%d,size:%d\r\n", 2, conn_handle, size);
    
    uint32_t send_size = 0;
    uint32_t mtu_size;
    ble_ghp_cntx_t *buffer_t = ble_ghp_get_cntx_by_handle(conn_handle);

    if ((conn_handle != BT_HANDLE_INVALID) && (buffer_t) ) 
    {
        mtu_size = bt_gattc_get_mtu((bt_handle_t)conn_handle);

        LOG_MSGID_I(GHP, "[BLE_GHP]mtu = %d\r\n", 1, mtu_size);
        if ((mtu_size - 3) < size) {
            send_size = mtu_size - 3;
        } else {
            send_size = size;
        }
        if (0 == send_size) {
            LOG_MSGID_I(GHP, "[BLE_GHP] ble_ghp_send_data send_size is 0!\r\n", 0);
            BLEGHP_MUTEX_UNLOCK();
            return 0;
        }

#ifdef BLE_GHP_LOW_POWER_CONTROL
        ble_ghp_set_remote_device_type(conn_handle, BLE_GHP_REMOTE_DEVICE_IOS);
        ble_ghp_update_connection_interval(conn_handle);
#endif   

        if (BT_STATUS_SUCCESS == ble_ghp_service_tx_send(conn_handle, buffer, send_size)) {
            LOG_MSGID_I(GHP, "[BLE_GHP] ble_ghp_send_data: send_size[%d]\r\n", 1, send_size); 
            BLEGHP_MUTEX_UNLOCK();  
            return send_size;
        }
    }
    BLEGHP_MUTEX_UNLOCK();
    return 0;
}

其订阅测试如下图所示:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值