• 泰凌微BLE(2):ATT自定义UUID并实现Notification数据传输


    1 理论

    GATT(Generic Attribute Protocol)的实质是由多个Attribute构成,每个Attribute都具有一定的信息量,当多个不同种类的Attribute组合在一起就反映出一个基本的service。
    在这里插入图片描述
    Attribute的基本内容和属性如下:

    (1)Attribute type:UUID

    UUID用来区分每个Attribute类型,全长为16字节。在/stack/service/hids.h中定义了一些标准的UUID。

    (2)Atribute Handle

    多个Attrbute组成一个Atribute Table,每个Attribute都有一个Attribute Handle值,用来区分不同的Attribute。slave和master建立连接后,master通过Service Discovery过程解析读取到slave的Attribute Table,并根据Attribute Handle的值来对应每一个不同的Attribute。

    (3)Attribute Value

    每个Attribute都有对应的Attribute Value,用来作为request、response、notification和indication的数据。


    为了实现slave端的GATT service,泰凌微的SDK设计了一个Attribute Table,该Table由多个基本的Attribute组成。

    Attribute结构体

    typedef struct attribute
    {
      u16  attNum;
      u8   perm;
      u8   uuidLen;
      u32  attrLen;    //4 bytes aligned
      u8* uuid;
      u8* pAttrValue;
      att_readwrite_callback_t w;
      att_readwrite_callback_t r;
    } attribute_t;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    参数

    (1)attNum

    • 表示当前Attribute Table中有效Attribute数目

      • 在BLE中Attribute Handle的值从0x0001开始,所以Attribute在数组中的下标号即Attribute Handle的值
    • 指定当前的service由哪几个Attribute构成

      • 每个service的第一个Attribute的UUID必须是GATT_UUID_PRIMARY_SERVICE(0x2800),这个AttributeattNum指定从当前Attribute开始往后数总共有attNumAttribute属于该service的组成部分
    • 除了第0项Attribute和每一个service首个Attribute外,其他所有的AttributeattNum的值都必须设为0

    (2)perm

    permission,指定当前AttributeClient访问的权限。权限如下,Attribute的权限可以为如下某个或组合:

    #define ATT_PERMISSIONS_READ 0x01
    #define ATT_PERMISSIONS_WRITE 0x02
    #define ATT_PERMISSIONS_AUTHEN_READ 0x61
    #define ATT_PERMISSIONS_AUTHEN_WRITE 0x62
    #define ATT_PERMISSIONS_SECURE_CONN_READ 0xE1
    #define ATT_PERMISSIONS_READ 0x01
    #define ATT_PERMISSIONS_WRITE 0x02
    #define ATT_PERMISSIONS_AUTHEN_READ 0x61
    #define ATT_PERMISSIONS_AUTHEN_WRITE 0x62
    #define ATT_PERMISSIONS_SECURE_CONN_READ 0xE1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • SDK暂不支持授权读和授权写

    (3)uuid、uuidlen

    UUID分两种:BLE标准的2 bytes UUID和Telink私有的16 bytes UUID。通过uuiduuidLen可以同时描述这两种UUID

    • uuid是一个指向flash的u8类型的指针,uuidlen为指针开始连续多少个字节

    (4)pAttrValue、attrLen

    表示Attributevalue的指针及其长度,该指针涉及写操作,所以可能在RAM中,也可能在Flash中

    (5)回调函数w

    typedef int (*att_readwrite_callback_t)(u16 connHandle, void* p);
    	p指针指向master写命令的具体数值,实际p指向一片内存,结构体为rf_packet_att_data_t
    
    int my_WriteCallback (void *p)
    {
        rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p;
        int len = pw->l2cap - 3;
        //add your code
        //valid data is pw->dat[0] ~ pw->dat[len-1]
        return 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    调用时机:当slave收到的Attribute PDUAttribute Opcode

    • opcode = 0x12,Write Request
    • opcode = 0x52,Write Command
    • opcode = 0x18,Execute Write Request

    Attribute可以没有回调函数,若不写则填空指针0。没有回调函数,则向pAttrValue指针所指向的区域写入master传来的值,长度为l2caplen-3

    • 如果pAttrValue指向flash 或 attrLen长度不够,master的写操作越界等情况需要自己写回调函数

    (6)回调函数r

    typedef int (*att_readwrite_callback_t)(u16 connHandle, void* p);
    
    • 1
    • connHandle为master和slave之间的连接句柄,slave应用填BLS_CONN_HANDLE; master应该填BLM_CONN_HANDLE
    • 该回调函数同样是可选的,opcode如下:
      • opcode = 0x0A,Read Request
      • opcode = 0x0C,Read Blob Request

    如果设置了回调函数,根据该函数的返回值决定是否回复Read Response/Read Blob Response。若为1则不回复,其他值则回复pAttrValue区域的长为attrLen的内容(没有设置回调函数也是这样)。

    • 也就是说,如果想修改内容,在回调函数中修改内存中的值后,返回一个非1值

    对于每个service都有一个Client Characteristic Configuration属性,它的UUID为0x2902,该描述符是给任何支持通知或指示功能的特性额外增加的,允许GATT服务端配置特征值为NotificationIndication

    • 1:使能通知功能 2:使能指示功能 0:禁止通知和指示功能
    • 当APP端Enable listensing时值为0x01

    2 实操

    现在我们希望能够自己声明一个UUID位0x3F00的服务,并设置两个UUID:0x3F01和0x3F02,一个用来读一个用来写。注意每个声明的UUID的前面必须还要声明UUID为0x2803(my_characterUUID)的特性声明,它的属性值指针需要指示这个特性是Read/Write/Notify/Broadcast等属性权限、属性句柄(对端设备可通过属性句柄来访问该属性,起始值为1,然后逐个加1)、还有其16位的UUID。

    1、添加Attribute
    app_att.c中找到my_Attributes变量,实际上就是填写前面介绍的Attribute结构体的内容。

    static const attribute_t my_Attributes[] = {
    
    	{ATT_END_H - 1, 0,0,0,0,0},	// total num of attribute,ATT_END_H 为Attribute Handle的总个数
    	/* 格式为:属性个数,权限,uuid长度,属性长度,uuid指针,属性值指针,写回调函数,读回调函数 */
    	......在最下面添加自己的服务......
    	// APP /
    	{6,ATT_PERMISSIONS_READ,2,2,(u8*)(&my_primaryServiceUUID),(u8*)(&ServerDecl ), 0},                           //①
    	{0,ATT_PERMISSIONS_READ,2,sizeof(my_AppDataIn),(u8*)(&my_characterUUID), (u8*)(my_AppDataIn), 0},            //②
    	{0,ATT_PERMISSIONS_WRITE,2,sizeof(DataInVal),(u8*)(&my_appDataInUUID),(u8*)(DataInVal), inComingDataCB, 0},  //③
    	{0,ATT_PERMISSIONS_READ,2,sizeof(my_AppDataOut),(u8*)(&my_characterUUID), (u8*)(my_AppDataOut), 0},          //④
    	{0,ATT_PERMISSIONS_RDWR,2,sizeof(DataOutVal), (u8*)(&my_appDataOutUUID),(u8*)(&DataOutVal), 0},              //⑤
    	{0,ATT_PERMISSIONS_RDWR,2,sizeof(DataCfgCCC),(u8*)(&clientCharacterCfgUUID), (u8*)(DataCfgCCC), cccUpdateCB},//⑥
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    下面来一行一行解释:
    ①首先需要使用UUID 0x2800来声明一个服务,服务的UUID自己指定,但不能和系统使用的重合

    #define GATT_UUID_PRIMARY_SERVICE        0x2800     //!< Primary Service
    #define SERVER_SERV_UUID                 0x3F00     //自定义的服务UUID
    static const u16 my_primaryServiceUUID = GATT_UUID_PRIMARY_SERVICE;
    static const u16 ServerDecl = SERVER_SERV_UUID;
    
    • 1
    • 2
    • 3
    • 4

    ②使用UUID 0x2803声明Service下的Characteristic

    #define GATT_UUID_CHARACTER 0x2803     //!< Characteristic
    static const u16 my_characterUUID = GATT_UUID_CHARACTER;
    #define APP_DATA_IN_UUID  0X3F01
    static const u8 my_AppDataIn[5] = {
    	CHAR_PROP_WRITE,
    	U16_LO(APP_DATA_IN_CD_H), U16_HI(APP_DATA_IN_CD_H),
    	U16_LO(APP_DATA_IN_UUID), U16_HI(APP_DATA_IN_UUID)
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • CHAR_PROP_WRITE代表该属性是用来写的,即客户端可以写数据到该Attribute
    • APP_DATA_IN_CD_HAttribute Handle,它从1开始递增,在app_att.h中的ATT_HANDLE变量中相应增加我们这边添加的6行Attribute的Handle定义

    ③声明刚刚声明的CharacteristicCharacteristic Value

    static const u16 my_appDataInUUID = APP_DATA_IN_UUID;
    /* 如果没有声明后面的回调函数,则客户端写的数据就默认写到数组中 */
    static uint8_t DataInVal[SIMPLESTREAMSERVER_DATAINOUT_LEN] = {0};
    
    typedef struct{
    	u8	type;       /* Data Channel Type */
    	u8  rf_len;     /* Data Channel Length */
    	u16	l2cap;      /* L2CAP报文的长度 */
    	u16	chanid;     /* Channel ID */
    	u8	att;        /* Attribute Opcode */
    	u8	hl;         /* Attribute handle低字节 */
    	u8	hh;         /* Attribute handle高字节 */
    	u8	dat[200];   /* 该值根据自己设置的实际的MTU修改 */
    }rf_packet_att_data_t;
    
    /* 写回调函数 */
    int inComingDataCB(u16 connHandle, void* p)
    {
    	rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p;
    	/* L2CAP报文长度减去Attribute Opcode和Attribute Handle的长度即实际传输的数据的长度 */
        int len = pw->l2cap - 3;
        //数据保存在pw->dat[0] ~ pw->dat[len-1]中
        
        return 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    通过Notification向指定的UUID传输消息的报文格式如下:
    在这里插入图片描述
    这就对应了上面结构体中的内容。

    ④⑤声明客户端可读的AttributeChracteristicChracteristic Value,与②③类似

    #define APP_DATA_OUT_UUID  0X3F02
    static const u16 my_appDataOutUUID = APP_DATA_OUT_UUID;
    static uint8_t DataOutVal[SIMPLESTREAMSERVER_DATAINOUT_LEN] = {0};
    /* 通知的形式为Notification */
    static const u8 my_AppDataOut[5] = {
    	CHAR_PROP_NOTIFY,
    	U16_LO(APP_DATA_OUT_CD_H), U16_HI(APP_DATA_OUT_CD_H),
    	U16_LO(APP_DATA_OUT_UUID), U16_HI(APP_DATA_OUT_UUID)
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    ⑥我们还希望能够实时获取客户端Client Characteristic Configuration的使能状态

    #define GATT_UUID_CLIENT_CHAR_CFG 0x2902     //!< Client Characteristic Configuration
    static const u16 clientCharacterCfgUUID = GATT_UUID_CLIENT_CHAR_CFG;
    /* 没有声明回调函数就写到这里 */
    static u8 DataCfgCCC[3] = {0,0,0};
    /*  客户端的写回调函数 */
    int cccUpdateCB(u16 connHandle, void* p)
    {
    	rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p;
        int res = (pw->dat[1] << 8) | pw->dat[0];
        //------------------
    	if(res == 0x0001)
    	{
    		/* ready */
    	}
        return 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    对于Client Characteristic Configuration的属性值如下:默认为0x0000,Notification使能时为0x0001。
    在这里插入图片描述
    资料:Bluetooth Core Specification V5.0

  • 相关阅读:
    Proxy 、Relect、响应式
    信息技术服务连续性策略
    Go通过reflect.Value修改值
    打破运维疆界:异构复杂网络环境的集中监控和管理
    【你问我答】Unity实现类似DNF地下城勇士的2D人物移动跳跃
    PyCharm配置远程Docker环境
    【C版本】静态通讯录与动态通讯录的实现,以及各自所存在的缺陷对比。(含所有原码)
    从 C 到 C++ 编程 — 基于 template 的泛型编程
    LeetCode-496-下一个更大元素
    PC端-bug记录
  • 原文地址:https://blog.csdn.net/tilblackout/article/details/127913416