蓝牙开发那些事儿(11)——BLE愉快地交互
上一章手机和flip4已经建立了连接,接下来的数据包格式就是data channel pdu了。
Data channel pdu的格式如下:
之前我们说过,和经典蓝牙不同的是,LE只有一个header,而BR的包头有两个——packet header和payload header,实际上这里的LLID类似于BR的payload header中的LLID,这里的NESN和SN类似于BR的packet header中的SEQN, 当然也并不完全一样,前者收发双方都维护一个sequence number而后者显然只有一方维护。
和经典蓝牙类似,data channel pdu也是根据header中的LLID分成control pdu和data pdu两种。
在建立连接后,master和slave会通过LLCP(link layer control protocl),通过control pdu对连接进行控制。
control pdu的格式如下:
其中opcode表示控制包的类型。
我们来看看,这次连接后,双方通过LLCP协议交互了一些信息。
然后就可以通过data pdu来交互上层信息了。
首先是一个att exchange mtu的,这是手机通知flip4其最大接收MTU size的。可以注意一下红线部分,首先LLID标识了这个包是个data pdu,然后l2cap层的cid似乎fixed的0x4,表示是att包,att payload的opcode表示是个exchange mtu request,手机的mtu是185。蓝牙4.0之前的标准,mtu只有23字节,这个mtu用来做OTA等固件升级的应用真的是很慢了,4.2之后mtu才扩大到255字节。
交换完MTU之后,手机需要了解到flip4这个server支持的服务、特性、特性描述等等信息,有点类似经典蓝牙的sdp环节。
首先需要获取service,获取service有两种方法,一是获取所有服务,一是根据uuid获取服务,这里选取的方法是前者,流程是这样的:
第一次发送的att_read_by_group_type_req的start handle一定是1,end handle是0xffff,attribute type是primary service(2800),然后server端会顺序从handle 1搜索服务,在att_read_by_group_type_rsp中回复找到的service,因为无法一次回复完,所以这个过程需要循环执行,下一次手机端发送的att_read_by_group_type_req数据包的start handle比att_read_by_group_type_rsp的end handle加1。
我们看一下flip4的第一个回复吧:
这个回复中包含了两个primary service,我们先记录在案,一共进行三次的service搜索到之后,一张service的列表就可以出来了:
Handle |
Attribute Type |
Attribute Value |
1 |
2800(primary service,蓝牙协会规定的assigned numbers) |
1801(GATT service,蓝牙协会规定的assigned numbers) |
5 |
2800 |
1800(GAP service,蓝牙协会规定的assigned numbers) |
10 |
2800 |
65786365-6C70-6F69-6E74-2E636F6D0000 |
16 |
2800 |
Fe8f |
我们知道profile是service的集合,找到所有service之后,还需要找到各个service的子集,包括include、characteristic等等,使用的命令都是ATT_READ_BY_TYPE_REQ。
如果找到characteristic,还要找到characteristic descriptor等等,使用的命令是ATT_FIND_INFOMATION_REQ。
include表示该service包含其他service,这个过程叫relationship discovery,这里我们没有这个过程。
Characteristic就比较重要了,因为service是characteristic的集合。
这个过程叫characteristic discovery:
看看我们找到的characteristic,按照格式,找到的characteristic,本身就是一个attribute,
有2个字节的attribute handel,这里是2,attribute type是2803(characteristic,这个在att_read_by_type_req中包含了,不会找其他的东西),attribute value包括1个字节的property(决定了读写权限等),2个字节的value handle(0003),和2个字节bluetooth UUID(2a05),assigned numbers表示service changed。
一般找到characteristic之后紧跟着是找characteristic descriptor:
最后,我们形成了一张service的总表:
Handle |
Attribute Type |
Attribute Value |
1 |
2800(primary service,蓝牙协会规定的assigned numbers) |
1801(GATT service,蓝牙协会规定的assigned numbers) |
2 |
2803(characteristic) |
{20,0003,service changed} |
5 |
2800(primary service) |
1800(GAP service,蓝牙协会规定的assigned numbers) |
10 |
2800(primary service) |
65786365-6C70-6F69-6E74-2E636F6D0000 |
11 |
2803(characteristic) |
{12(read,notify), 000c, 65786365-6C70-6F69-6E74-2E636F6D0001} |
14 |
2803(characteristic) |
{08(write), 000f, 65786365-6C70-6F69-6E74-2E636F6D0002} |
16 |
2800(primary service) |
Fe8f |
从这个表可以看出,10号service是我们提供读写服务的核心service,其中,有0x000c号handle可以用于读数据,0x000f号handle可以用于写数据,下面可以看到app就利用这两个handle号和flip4进行交互:
这是收到的notify数据包:
这是write的数据包:
具体的数据格式,就是上层app需要关心的内容了,和协议无关。