蓝牙BLE学习-GATT和ATT

news2024/11/24 16:54:12

1. GATT

GATT-Generic Attribute profle-通用属性配置文件。GATT层是传输真正数据所在的层。包括了一个数据传输和存储架构以及其基本操作。GATT用来规范attribute中的数据内容,并运用group(分组)的概念对attribute进行分类管理。没有GATT,BLE协议栈也能跑。但互联互通就会出问题,也正是因为有了GATT和各种各样的应用profile,BLE摆脱了Zigbee等无线协议的兼容性困境,成为了出货量最大的2.4G无线通信产品。

GATT定义了两类角色:服务端(server)和客户端(client),GATT角色无需和GAP角色绑定,但是可能由更高层的规范进行指定。

除了GAP定义角色外(GAP定义广播者,扫描者,外围设备,中央设备),BLE还定义了另外2种角色:GATT服务端和GATT客户端。这两种角色完全独立于GAP的角色。提供数据的设备被称为GATT服务端,访问GATT服务端而获得数据的设备被称为GATT客户端。即一个设备可以同时作为客户端和服务端。通常情况下,外围设备为服务端,提供数据。手机作为客户端,访问数据。

 一个GATT服务器通过一个称为属性表的表格组织数据,这些数据就是用于真正发送的数据。

 2.属性ATT

属性协议(Attribute Protocol)简称ATT。是GATT和GAP的基础。

就属性来说,一个属性其实就是一条数据,属性是BLE数据提供单元,也是蓝牙空中传播数据的最上层,BLE开发过程中接触最多的就是这一层。

一个属性包含了句柄、UUID、值。数据的结构如下图:

2.1 Attribute Handle

属性句柄(Attribute Handle)占2个字节,犹如指向属性实体的指针,对端设备可以通过属性句柄来访问该属性

属性句柄值的范围:0x0000 ~ 0xFFFF

2.2 Attribute Type

属性类型(Attribute type)是用来区分当前属性是服务项还是特征值等,一般用UUID来表示。

UUID(universally unique identifier,通用唯一识别码)是一个软件构建标准,并非BLE独有的概念,一个合法的UUID,一定是随机的,全球唯一的,不应该出现两个相同的UUID。但是在一个GATT表中可能有许多属性,这些属性可能有相同的UUID。

BLE的属性类型主要有四个大类

 

Primary Service(首要服务项):0x2800

Secondary Service(次要服务项):0x2801

Include(包含服务项):0x2802

Characteristic(特征值):0x2803

常用的UUID: 

Characteristic Extended Properties(特性扩展性质):0x2900

Characteristic User Description(特性用户描述):0x2901

Client Characteristic Configuration(客户端特性配置):0x2902

Server Characteristic Configuration(服务器特性配置):0x2903

Characteristic Presentation Format(特性表示格式):0x2904

Characteristic Aggregate Format(特性聚合格式):0x2905

客户端特性配置描述符:支持通知或指示的特性必须使用该描述符

取值范围

        0x1800 – 0x26FF :服务项类型

        0x2700 – 0x27FF :单位

        0x2800 – 0x28FF :属性类型

        0x2900 – 0x29FF :描述符类型

        0x2A00 – 0x7FFF :特征值类型

长度

UUID即可以是2字节也可以是16字节。因为一些常用的UUID,为了减少传输的数据量,BLE协议做了一个转换约定。给定一个固定的16字节模板,只设置2个字节为变化量,其他为常量。2字节的UUID在系统内部会被替换,进而转换成保准的16字节的UUID。

UUID模板为:

而2字节的UUID则替代上边的XXXX。例如心率测量特性使用0X2A37作为它的16位UUID,因此它完整的16字节的UUID为:0x00002A37-0000-1000-8000-008005F9B34FB。

蓝牙技术联盟所用的基本UUID不能用于任何定制的属性、服务和特性。对于定制的属性,必须使用完整的16字节UUID。

2.3 Attribute Value

 属性值,就是该条属性数据包含的有效负载。

2.4 Attribute Permissions

属性权限/属性许可,作用是保护Attribute Value。

Attribute Permissions不会直接在空中包中体现,而是隐含在ATT命令的操作结果中。

权限属性:

访问权限(Access Permission)-只读,只写,读写

禁止访问权限(No Access)-禁止读或者写

加密权限(Encryption Permission)-加密,不加密

认证权限(Authentication Permission)-需要认证,无需认证。认证是指相互确认对方身份。完成认证流程的两个设备,双方建立信任关系,二者之间的通信通道即可认为是安全的。BLE中,认证过程就是配对。

 授权权限(Authorization Permission)-需要授权,无需授权

 签名权限(Signed Permission)-签名后才能读或写,这个用得比较少

 认证和授权功能很容易混淆,其英文拼写也很相似。从上面的概念上看,授权要求设备必须是可信任的,因此授权的管控等级要高于认证--认证的设备未必被授权,授权的设备一定是认证的。理解二者关系后,需要引入一个概念:Trusted Device(可信任设备)一个没有经过认证的设备,被称为Unknown Device(未知设备);经过了认证后,该设备会在绑定设备信息中被标记为Untrusted,被称为Untrusted Device(不可信设备);经过了认证,并且在绑定信息中被标记为Trusted的设备被称为Trusted Device(可信设备)。

授权要求设备为Trusted Device(可信任设备)。在实际使用中,经过配对以后设备即为Untrusted Device--认证,在代码中调用API可以设置设备为Trusted Device--授权。

大部分的空中操作事件都是采用句柄来进行的,因为句柄能够唯一识别各个属性。如何使用特性依据他的性质,特性的性质包括:

没有回应的写

通知:客户端发请求服务端,不需要服务端回复一个响应

指示:客户端发请求给服务端,需要服务端回复一个响应。

写和没有回应的写

这两个权限允许GATT客户端写一个值到GATT服务端的一个特性中。他们之间不同的地方在与没有回应的写事件没有任何应用层上的确认或回应。

读性质标明一个GATT客户端可以读取在GATT服务端中特性的值。

通知和指示

通知和指示性质允许GATT服务端在其某个特性改变的时候对GATT客户端进行提醒。通知和指示之间不同之处在与指示有应用层上的确认,而通知没有。

注:Client和Server之间是通过ATT PDU来通讯的,ATT PDU主要包括4类:读、写、通知和指示。如果一个命令需要response,那么会在相应的指令后面加上request。如果一个命令只需要ACK而不需要response,那么后面就不会带request。这里要特别强调一点,BLE所有命令都是“必达”的,也就是说每个命令发出去之后,会立刻等ACK信息,如果收到了ACK包,发起方认为命令发送完成。否则发起方会一直重发该命令,直到超时导致BLE连接断开。换句话说,只要BLE没有断开,那么发送的数据包,一定会被对方收到。

那既然每个ATT命令都必达对方,那么为什么还需要request呢?如果一个命令带有request后缀,那么发起方就可以收到命令的response包,这个response包在应用层是有回调事件的,而ACK包在应用层是没有回调事件的。所以采用request/response方式,应用层可以按顺序地发送一些数据包,这个在很多应用场景中是非常有用的。相反,如果对应用层数据包的顺序没有要求,那么就可以不使用request/response形式。另外request/response有一个副作用:大大降低通信的吞吐率。因为request/response必须在不同的连接间隔中出现。也就是说,如果在间隔1中发送了一个request命令,那么response包必须在间隔2或者稍后间隔中回复,而不能再间隔1中回复。这就导致两个连接间隔最多只能发一个数据包,而不带quest后缀的ATT命令就没有这个问题,在同一个连接间隔中,可以同时发送多个数据包,这样就大大提供数据的吞吐率。

常用的带request的命令:所有读命令,写,指示等。而常用的不带request的命令有没有回应的写,通知等。

2.5 特性

一个特性至少包含2个属性:一个属性用于声明,一个属性用于存放属性的值。

所有通过GATT服务传输的数据必须映射成一系列的特性,可以把特性中的这些数据看成是一个个捆绑起来的数据。每个特性就是一个自我包容而独立的数据点。例如,如果几个数据总是一起变化,那么可以把它们集中在一个特性里。

2.6 描述符

 任何在特性中的属性不是定义为属性值,就是为描述符。描述符是一个额外的属性以提供更多的特性信息。它提供一个人类可识别的特性描述的实例。

 然而,有一个特别的描述符需要特别注意:客户端特性配置表述符(Client Characteristic Configuration Descriptor,CCCD),这个描述符是给任何支持通知和指示功能的特性额外增加的。在CCCD中写入“1”使能通知功能,写入“2”使能指示功能,写入”0“同时禁止通知和指示功能。

2.7 服务

一个服务包含一个或多个特性,这些特性是逻辑上相关的集合体。

GATT服务一般包含几块具有相关性的功能,比如特定传感器的读取和设置,人机接口的输入和输出。组织具有相关的特性到服务中即实用又有效。因为它使得逻辑上和用户数据上的边界变得更加清晰,同时它也有助于不同应用程序间代码的重用。GATT基于SIG蓝牙技术联盟官方设计,SIG建议根据用户自己的规范设计自己的profile。

2.8 profile(数据配置文件)

 一个profile文件可以包含一个或多个服务,一个profile文件包含需要的服务信息或者是与设备如何交互的配置文件选项信息。设备的GAP和GATT的角色都可能在数据的交换过程中改变,因此,这个文件应该包含广播的种类、所使用的连接间隔、所需的安全等级等信息。

注:一个Profile中的属性表不能包含另一个属性表。

2.9 标准的定制服务和特性

SIG蓝牙技术联盟已经定义了一些profile、服务、特性和根据协议栈的GATT层定义的属性。但是协议栈中只实现了一部分应用的BLE服务。那就意味着,只要协议栈支持GATT,就可能为一个应用建立一个它需要的profile和服务。既然在一个应用中可以支持profile和服务,那么就可以在这个应用中建立一个个人定制的服务。

每个设备都包含以下必要的特征值和服务项:

PROFILE

        Generic Access Service(Primary Service)

                Device Name(Characteristic)

                Apperance(Characteristic)

        Generic Attribute Service(Primary Service)

                Service Changed(Characteristic)

                CCCD(Descriptor)

服务项这种类型本身并不包含数据,仅仅相当于是一个容器,用来容纳特征值。特征值用来保存用户数据,但是它也有自己的UUID。在处理特征值所携带的用户数据之前,需要先对特征值自身进行声明。

特征值在系统中的表达形式是:声明 + 特征值属性。比如上边Generic Access Service服务项的Device Name特征值,它在GATT数据库中表示方式是:

Characteristic 声明: 0x0002, 0x2803, access_property, 0x2A00

Characteristic 项:   0x0003, 0x2A00, access_property, data

其中,第一列2个字节代表属性句柄。第二列2个字节代表属性类型(UUID)。0x2803表示该项为“特征值的声明”。0x2A00表示该特征值是Device Name。在第一行特征值声明中,最后一项的属性值就是该特征值的UUID。可以看到,第一行声明里面的属性值0x2A00,而第二行属性值自己的属性类型(UUID)也是0x2A00,信息冗余了。原因是,假如特征值的UUID为自定义的16字节UUID,在特征值的属性类型(UUID)中,只能存放2个字节,而16字节的UUID全称,就只能存放在特征值声明的属性值项中。例如:

Characteristic 声明: 0x0002, 0x2803, access_property, 0x0000ABCD1212efde1523785feabcd123

Characteristic 项:   0x0003, 0xABCD, access_property, data

BLE的属性体系在系统中以GattDB表示,即属性数据库。在CyBle_gatt.c文件中,可以看到cyBle_gattDB变量,如下:

const CYBLE_GATTS_DB_T cyBle_gattDB[0x10u] = {

    { 0x0001u, 0x2800u /* Primary service                     */, 0x00000001u /*           */, 0x0007u, {{0x1800u, NULL}}                           },

    { 0x0002u, 0x2803u /* Characteristic                      */, 0x00000201u /* rd        */, 0x0003u, {{0x2A00u, NULL}}                           },

    { 0x0003u, 0x2A00u /* Device Name                         */, 0x00000201u /* rd        */, 0x0003u, {{0x0009u, (void *)&cyBle_attValuesLen[0]}} },

    { 0x0004u, 0x2803u /* Characteristic                      */, 0x00000201u /* rd        */, 0x0005u, {{0x2A01u, NULL}}                           },

    { 0x0005u, 0x2A01u /* Appearance                          */, 0x00000201u /* rd        */, 0x0005u, {{0x0002u, (void *)&cyBle_attValuesLen[1]}} },

    { 0x0006u, 0x2803u /* Characteristic                      */, 0x00000201u /* rd        */, 0x0007u, {{0x2A04u, NULL}}                           },

    { 0x0007u, 0x2A04u /* Peripheral Preferred Connection Par */, 0x00000201u /* rd        */, 0x0007u, {{0x0008u, (void *)&cyBle_attValuesLen[2]}} },

    { 0x0008u, 0x2800u /* Primary service                     */, 0x00000001u /*           */, 0x000Bu, {{0x1801u, NULL}}                           },

    { 0x0009u, 0x2803u /* Characteristic                      */, 0x00002201u /* rd,ind    */, 0x000Bu, {{0x2A05u, NULL}}                           },

    { 0x000Au, 0x2A05u /* Service Changed                     */, 0x00002201u /* rd,ind    */, 0x000Bu, {{0x0004u, (void *)&cyBle_attValuesLen[3]}} },

    { 0x000Bu, 0x2902u /* Client Characteristic Configuration */, 0x00000A04u /* rd,wr     */, 0x000Bu, {{0x0002u, (void *)&cyBle_attValuesLen[4]}} },

    { 0x000Cu, 0x2800u /* Primary service                     */, 0x00000001u /*           */, 0x0010u, {{0xCBBBu, NULL}}                           },

    { 0x000Du, 0x2803u /* Characteristic                      */, 0x00001A01u /* rd,wr,ntf */, 0x0010u, {{0xCBB1u, NULL}}                           },

    { 0x000Eu, 0xCBB1u /* Custom Buffer                       */, 0x00011A04u /* rd,wr,ntf */, 0x0010u, {{0x00C8u, (void *)&cyBle_attValuesLen[5]}} },

    { 0x000Fu, 0x2901u /* Custom Descriptor                   */, 0x00010001u /*           */, 0x000Fu, {{0x001Cu, (void *)&cyBle_attValuesLen[6]}} },

    { 0x0010u, 0x2902u /* Client Characteristic Configuration */, 0x00010A04u /* rd,wr     */, 0x0010u, {{0x0002u, (void *)&cyBle_attValuesLen[7]}} },

};

通过注释可以看到,每个特征值都有声明和特征值项两部分组成。

由于每个属性的句柄是递增的,因此属性的声明顺序会影响句柄的计算。

描述符是特征值的补充信息,挂载在特征值之下,它可以开辟一段数据空间以携带数据,客户端可以像操作特征值一样对其进行读写,但是描述符弱于特征值,它不具备Notify/Write等读写属性,远不如特征值灵活。有一种描述符叫CCCD(Client Characteristic Configuration Description),当对特征值设置Notify或Indication时,用以控制Notify和Indication的使能情况。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1457450.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

幻兽帕鲁游戏联机的时候,显示“网络连接超时”怎么解决?

如果你在游戏联机的时候,显示“网络连接超时”,可以检查下: 1、前提是你已经按照教程部署成功 2、检查防火墙有没有忘记设置,协议是UDP(只有TCP不行,一定要有UDP),端口是否填了8211&…

Vue Suspense

<Suspense> 是一项实验性功能。它不一定会最终成为稳定功能&#xff0c;并且在稳定之前相关 API 也可能会发生变化。 <Suspense> 是一个内置组件&#xff0c;用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌套异步依赖项解析完成&a…

cookie封装,使用更方便

cookie封装 当提到"cookie封装"&#xff0c;通常是指在开发中对浏览器cookie的处理进行封装和管理&#xff0c;以简化代码和提高可维护性。在Web开发中&#xff0c;cookie是一种用于存储少量数据的小文件&#xff0c;存储在用户的浏览器中。它们被广泛用于跟踪用户会…

循环结构(含练习题)

当循环次数或范围确定时&#xff0c;多用for循环&#xff0c;反之多用while循环 循环结构一般由四部分组成&#xff1a; 初始化语句&#xff0c;在循环开始最初执行&#xff0c;并且只执行一次条件判断、步进语句、循环体 for & foreach for循环&#xff0c;循环体可以执…

22-k8s中pod的调度-亲和性affinity

一、概述 在k8s当中&#xff0c;“亲和性”分为三种&#xff0c;节点亲和性、pod亲和性、pod反亲和性&#xff1b; 亲和性分类名称解释说明nodeAffinity节点亲和性通过【节点】标签匹配&#xff0c;用于控制pod调度到哪些node节点上&#xff0c;以及不能调度到哪些node节点上&…

MySQL学习记录——십이 事务

文章目录 1、了解事务2、事务提交3、事务隔离级别1、隔离性和隔离级别2、查看、设置隔离级别3、读未提交4、读提交5、可重复读6、串行化7、总结 4、事务一致性5、事务隔离性1、隐藏字段2、undo日志3、模拟MVCC4、Read View 6、读提交RC、可重复读RR的区别 1、了解事务 MySQL内…

【C语言必刷题】3.二分查找

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有…

成功靠运气还是能力?我写了一个运气模拟器,告诉给你答案

前端训练营&#xff1a;1v1私教&#xff0c;终身辅导计划&#xff0c;帮你拿到满意的 offer。 已帮助数百位同学拿到了中大厂 offer。欢迎来撩~~~~~~~~ 视频版可直接访问&#xff1a;https://www.bilibili.com/video/BV1ct421b7Q7/?vd_source391a8dc379e0da60c77490e3221f097a…

oauthlib,一个强大的 Python 身份校验库!

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站零基础入门的AI学习网站~。 目录 ​编辑 前言 什么是 OAuthLib&#xff1f; 安装 OAuthLib OAuthLib 的主要功能 OAuthLib 的用法 实现…

自己动手写编译器:使用 PDA 实现增强和属性语法的解析

在前面章节中我们了解了增强语法和属性语法&#xff0c;特别是看到了这两种语法的结合体&#xff0c;本节我们看看如何使用前面我们说过的自顶向下自动机来实现这两种语法结合体的解析&#xff0c;这里使用的方法也是成熟编译器常用的一种语法解析算法。 首先我们先给出上一节…

区块链金融科技:技术融合与挑战应对【文末送书-16】

文章目录 前言一.区块链与金融科技的融合&#xff1a;革新金融格局的技术之光1.1区块链技术简介1.2 区块链在金融科技中的应用 二.智能合约2.1 去中心化金融&#xff08;DeFi&#xff09;2.2区块链对金融科技的影响2.3数据安全性 三.区块链与金融科技【文末送书-16】3.1 粉丝福…

如何训练Ai把古诗《如梦令》描写的意境画出来?

常记溪亭日暮&#xff0c;沉醉不知归路。兴尽晚回舟&#xff0c;误入藕花深处。争渡&#xff0c;争渡&#xff0c;惊起一滩鸥鹭。 古诗《如梦令》以其优美的语言和丰富的意境给人留下深刻的印象。今天&#xff0c;我们将借助AI的力量&#xff0c;将这首诗的意境转化为视觉画面…

x86使用页表实现虚拟内存原理分析---使用代码分析

分页机制 这一部分在手册第四章 视频讲解可以看这一个课程 在不使用分页机制的时候, 我们看到的是物理内存, 物理内存有多大, 我们就可以使用多大的内存 使用内存分页机制, 我们就可以扩充访问的地址范围, 也可以实现权限的细分, 实际上就是实现虚拟内存, 将地址进行映射, 看到…

希尔排序算法

目录 ShellSort希尔排序 整体思路 图解分析 【1】预排序 单组排序 多组并排 【2】直接插入排序 关于gap取值 总代码实现 时间复杂度 ShellSort希尔排序 希尔排序法又称缩小增量法。 希尔排序法的基本思想是&#xff1a;先选定一个整数&#xff0c;把待排序文件中所有…

产品经理学习-产品运营《流程管理》

如何进行流程管理 信息可视化 甘特图-流程管理思维导图-方案讨论原型图-活动文档 明确责任制 分工明确&#xff0c;关键环境有主负责人通过时间倒推督促管理 沟通技巧 明确共同利益以结果激励做好信息同步 如何进行监控活动效果 监控活动的效果是要监控数据 活动每个环境的…

MySQL学习记录——십일 索引

文章目录 1、了解索引2、聚簇、非聚簇索引3、操作1、主键索引2、唯一键索引3、普通索引4、注意事项 4、全文索引 1、了解索引 MySQL服务器是在内存中的&#xff0c;所有数据库的CURD操作都是在内存中进行&#xff0c;索引也是如此。索引是用来提高性能的&#xff0c;它通过组织…

[嵌入式系统-16]:RT-Thread -2- 主要功能功能组件详解与API函数说明、API参考手册入口

目录 一、RT-Thread主要功能组件 二、内核组件 2.1 概述 2.2 API 三、设备驱动 3.1 概述 3.2 API 四、通信组件 4.1 概述 4.4 API 五、网络组件 5.1 概述 5.2 API 5.3 补充&#xff1a;MQTT协议 六、文件系统 6.1 概述 6.2 API 七、GUI 组件 7.1 概述 7.2 …

进程终止与进程等待

fork 函数 fork 函数是 Linux 中一个非常重要的函数&#xff0c;它的作用是从已存在的进程中创建一个新进程。这个新进程就是当前进程的子进程。 fork() 函数使用方法&#xff1a;它在头文件 #include <unistd.h> 中&#xff0c;函数原型为 pid_t fork(void); 用一个…

CMNet:Contrastive Magnification Network for Micro-Expression Recognition 阅读笔记

AAAI 2023的一篇文章&#xff0c;东南大学几位老师的工作&#xff0c;用于做微表情识别中的运动增强工作&#xff0c; 以下是阅读时记录的笔记。 摘要&#xff1a; However,existing magnification strategies tend to use the features offacial images that include not onl…

Minio通过Url直接访问附件

1、修改桶策略为public 2、http://locahost:9000/桶名/文件名即可 访问该文件&#xff0c;自己浏览 http://127.0.0.1:9000/netcore/netcore/9d4a526b-a477-46a3-90c7-a668354e3b46.png