关注、星标公众号,直达精彩内容
来源:https://blog.csdn.net/weixin_35016347/article/details/108011878
整理:技术让梦想更伟大 | 李肖遥
蓝牙技术起源于爱立信在1994年提出的方案,旨在解决移动电话和其他配件之间进行低功耗、低成本的无线通信连接的方法。
蓝牙发展历史第一代蓝牙主要是指90年代的V1.0~V1.2版本,是SEO靠我关于段距离通信的早期探索,此时还存在许多问题,应用不是特别广泛
第二代蓝牙主要是00年中V2.0~V2.1版本,新增了EDR(Enhanced Data Rate)技术提高传输速率,以及体验及安全
第三代SEO靠我蓝牙主要是00年末V3.0版本,新增了802.11 WiFi协议,引入了AMP(Generic Alternate MAC/PHY)交替射频技术,极大的提高了传输速率并降低功耗
第四代蓝牙是10年以来的SEO靠我V4.0~V4.2版本,主推LE(Low Energy)低功耗,大约仅消耗十分之一,将三种规格,包括经典蓝牙、高速蓝牙、和蓝牙低功耗,集中在一起形成一套综合协议规范
第五代蓝牙是16年开始提出的V5.0SEO靠我版本,主要是为了支持物联网,在功耗、传输速率、有效传输距离、数据包容量方面都做了极大的提升
下面的分析都是基于V4.1版本,方便入门,可以理解很多核心协议的设计思想
蓝牙技术包SEO靠我含蓝牙发展过程中的两套技术,但是这两套原理和实现都不一样,也无法实现互通
Basic Rate(BR)/AMP
最初的蓝牙技术,包括可选的EDR(Enhanced Data Rate)技术和交替使用的MASEO靠我C层和PHY层扩展 AMP(Alternate MAC and PHY layer extension)【优化传输速度的过程】
解释:蓝牙诞生之初使用的BR技术,传输速率很低,随着发展而变得无法支持,所SEO靠我以引入了EDR,这时还没有修改软硬件架构,但是之后又落伍了,所以直接引入了WiFi的底层协议,也就是MAC/PHY扩展,但这部分的实现就无法直接更替,所以BR/EDR只能与AMP交替使用
Low EneSEO靠我rgy(LE)
蓝牙低功耗,则不关心传输速率,而是从降低功耗的角度实现的另一套技术,跟前面的协议没有丝毫关系
蓝牙协议将蓝牙整体分成了两层架构,底层是核心协议,描SEO靠我述了蓝牙核心技术的基础和规范,应用层协议则基于具体需求,使用核心协议提供的机制,实现不同的功能策略
核心协议包含两部分,Host和Controller,这两部分在不同的蓝牙协议版本中略有区别,但大致上是SEO靠我,Controller完成硬件侧的规范制订,包括信号调制解调,会抽象出用于通信的逻辑链路,可能存在一个或多个,如LE Controller、BR/EDR Controller;Host则在逻辑链路的基SEO靠我础上完成更友好的封装,屏蔽掉技术细节,方便应用层对数据的使用
蓝牙协议也采用层次结构,自下而上依次为物理层、逻辑层、L2CAP层和应用层
protocol应用层(App LayerSEO靠我)为不同场景定义规范,提出Profile(一项服务)的概念,实现各种应用功能
L2CAP(Logical Link Control and Adaptation Protocol Layer)
逻辑链路控SEO靠我制和适配协议,负责管理逻辑链路,使得不同应用可共享一个逻辑链路,类似端口的实现
在逻辑链路的基础上,抽象出与具体技术无关的数据传输信道,如单/广播,然后对上以L2CAP channel endpointSEO靠我s的概念,为不同应用程序提供独立的传输通道
逻辑层(Logical Layer)
提供设备对象之间逻辑传输,在物理层的基础上,建立逻辑信道,主要基于传输类型来划分,包括控制类传输(负责底层物理链路的管理)SEO靠我、用户类传输(负责用户数据传输)和其他特殊类型的传输
不同的逻辑信道(Logical Link)会在下层对应Logical Transport,实现流控、应答、重传等机制
物理层(Physical LaySEO靠我er)
负责提供数据传输的物理通道
物理链路(Physical Link):对物理信道的进一步封装
物理信道(Physical channel):三种蓝牙技术都使用相同的频段和频率范围,但是具体实现都不一样SEO靠我
Advertisement Broadcast Channel:用于设备间无连接广播通信,包括发现/连接操作
Piconet channel:用于连接状态下通信
Inquiry Scan PhysicalSEO靠我 Channel:用于发现操作,即搜索/被搜索
Page Scan Physical Channel:用于连接操作,即连接/被连接
Basic Piconet Physical Channel:用于连接状SEO靠我态下通信,使用79个跳频点
Adapted Piconet Physical Channel:用于连接状态下通信,使用较少RF跳频点
Synchronization Scan Channel:用于无连接的SEO靠我广播通信
BR/EDR频段分成了79个channel,每个占1M带宽;采用跳频技术(Hopping),即物理信道随机占用某一channel;定义了五种物理信道,每次只能在一种物理信道上通信,采用时分方式SEO靠我
AMP直接使用WIFI的物理层规范,只有一个物理信道,用于已连接设备之间的高速数据通信
LE频段分成了40个channel,每个占2M带宽;有两种物理信道,每次只能在一种物理信道上通信,采用时分方式
实现一个BLE应用,需要一个支持BLE射频的芯片,然后基于一个与芯片配套的协议栈,开发蓝牙应用。
协议栈的作用就是软件和硬件之间的桥梁,对应用数据进行封包然后生成可以通过射频发送SEO靠我的空中数据包及其逆向过程。
BLE-protocolPhysical Layer(PHY)
蓝牙通信系统的物理层,是免费ISM频段,整个频带分成40份,每份带宽2MHz;此外还定义了RF收发相关的特性,如SEO靠我发射功率、调制解调方式等
Link Layer(LL)
解决在有限物理信道上传输远多实际信道数量的数据,即信道共享,然后为通信实体创建看似独享的逻辑信道,以及解决传输过程中的校验、重传等问题
LL中的信道设SEO靠我计:BLE系统基于通信场景,在40个物理信道中选取三个作为广播信道,处理数据量小、发送不频繁、时延不敏感的场景,存在的问题就是不可靠、效率低、不安全;另外的场景则在剩下的37个信道中选取一个为双方建立SEO靠我单独信道,并且为了抗干扰采用跳频技术
为此,LL为通信双方实体定义了以下状态及切换条件
BLE-state - Standby:初始状态,不收发数据,接受上层协议命令与其他状态切换 - ASEO靠我dvertising:通过广播发送数据的状态,建立连接后可进入Connection - Scanning:接收广播的数据的状态 - Initiating:特殊的接收状态SEO靠我,类似Scanning,接收Advertiser广播的连接数据,建立连接后进入Connection - Connection:建立连接后拥有单独的通道 12345这里会SEO靠我使用空中接口协议(Air Interface Protocol,AIP)来负责实体之间的数据交换和状态切换
Host Controller Interface(HCI)
定义Host和ContorllerSEO靠我之间的通信协议,如两个芯片之间的串口
L2CAP
逻辑控制和适配协议的工作就是实现逻辑信道的多路复用(multiplexing),对上层数据进行分割和重组,以及后续的流控、错误控制和重传等
多路复用思想:将SEO靠我要发送的数据分割成一个个数据包(Packet Data Unit,PDU),添加包含特定ID的头部,接收方解析头部ID进行重组
多路复用实现
基于连接:L2CAP会为每个逻辑信道分配一个编号(ChanneSEO靠我l ID,CID),有些CID会有固定用途
基于协议(略)
Attribute Protocol(ATT)
属性协议主要是针对物联网场景,核心思想就是将采集的信息或控制的命令以属性的形式抽象出来,提供接口供SEO靠我远端设备读写
采用C/S形式,信息提供方为ATT Server,如传感器,访问方为ATT Client
为每个Attribute定义了三个属性
Type,即Attribute的类型,使用UUID区分
HandSEO靠我le,服务端用来唯一标识Attribute的16-bit数值
Value,Attribute的值
为每个Attribute定义了一系列权限,方便服务端控制客户端的行为,包括访问/加密/认证/授权
对于不同的SEO靠我Attribute,客户端对服务端的访问方式也不一样,包括Find/Read/Write
传输过程是在L2CAP的基础上,使用基于通道的多路复用,CID为0x0004
Generic Attribute SEO靠我Profile(GATT)
通用属性配置文件,Attribute只是将信息(或者说通信数据)做一下抽象,但是真正对抽象的信息做分类管理则是GATT来完成,形成profile的概念(解决了很多无线协议的兼SEO靠我容问题),profile可以理解成应用场景或者使用方式
GATT提供了这样一种通用的、信息存储与共享的profile framework,实现BLE双向通信
GATT的层次结构
GATT_profile_hSEO靠我ierarchyProfile位于最顶层,不是真正存在的配置文件,而是一个或多个场景相关的service的抽象集合
Service(服务)是一种行为的抽象,具有唯一标识UUID,每个service包含一SEO靠我个或多个Characteristic,也可以通过include的方式包含其他service
Characteristic(特征)可以理解成一个属性,是真正与设备通信相关的,数据发送和接收的最基本单位,通SEO靠我过对特征的读写实现蓝牙双向通信,它由一个Propertities(定义Value的使用规范和Descriptor的访问规范)、一个Value(特征的实际取值)和一个或多个Descriptor(ValuSEO靠我e相关的描述信息)组成,每个特征也具有自己的唯一标识,但是有三种形式:
16-bit是官方认证,收费,Bluetooth_Base_UUID 为 00000000-0000-1000-8000-0080SEO靠我5F9B34FB
16-bit转128-bit,格式为 0000xxxx-0000-1000-8000-00805F9B34FB
32-bit转128-bit,格式为 xxxxxxxx-0000-1000SEO靠我-8000-00805F9B34FB
事实上,目前几乎所有的BLE应用都基于GATT实现通信
GATT通信基于C/S模型,外围设备作为Server端,维护ATT结构及产出数据,中心设备作为client端,SEO靠我请求连接获取数据
GATT连接对外围设备是独占的,即一个外围设备同时与一个中心设备建立连接,一个中心设备可同时与多个外围设备建立连接
Security Manager(SM)
安全管理协议主要负责BLE通信SEO靠我过程中安全相关的内容,包括认证、加密这些过程
Generic Access Profile(GAP)
通用访问配置文件,定义了蓝牙设备的通用的访问功能,与GATT的数据通信过程对应,处理无连接及连接建立过SEO靠我程的通信,也就是为广播、扫描、发起连接这些过程定义统一规范
定义了用户接口的基本参数,包括蓝牙地址、名称、pincode、class等概念
定义了设备的角色:
Broadcaster Role:正在发送adSEO靠我vertising events的设备
Observer Role:正在接收advertising events的设备
Peripheral Role:接受Link Layer连接的设备(对应Link LSEO靠我ayer的slave角色)
Central Role,发起Link Layer连接的设备(对应Link Layer的master角色)
定义了通信的过程和操作模式:
Broadcast mode and oSEO靠我bservation procedure:实现单向的、无连接的通信
Discovery modes and procedures:实现蓝牙设备的发现操作
Connection modes and procSEO靠我edures:实现蓝牙设备的连接操作
Bonding modes and procedures:实现蓝牙设备的配对操作
使用场景
单向、无连接的数据通信,发送者使用广播信道发送数SEO靠我据,接受者扫描接收数据
连接建立阶段
协议层次
GAP:以应用程序角度进行功能封装,提供一套统一的、通用的广播规范
HCI:将LL提供的功能抽象成Command/Events的形式,供上层使用
LL:负责广播通SEO靠我信相关功能的定义和实现,包括信道选择、链路状态定义、PDU定义、设备过滤机制等
LL
信道选择。BLE将蓝牙频段分成了40个物理信道,综合考虑(抗干扰等)后将其中三个作为广播信道,频段为0/12/39,编SEO靠我号是37-39
链路状态。参与广播的BLE设备,总是处于这三种状态之一
Advertising:广播状态,周期性地广播,数据发送方
Scanning:扫描状态,扫描并接受广播数据,数据接收方
InitiatiSEO靠我ng:初始化状态,扫描到可连接的广播时,发起连接请求,连接发起方
PDU(Packet Data Unit)格式
pduType是指PDU的类型,如不同的状态下也有不同的消息类型,TxAdd和RxAdd都SEO靠我是地址类型flag,针对不同的type有不同的含义,RFU都是保留字段,Length标明payload的长度
Payload内容
StateTypeDescriptionsPayloadlengthDesSEO靠我criptionsAdvertisingADV_IND常规广播,可连接可扫描AdvA6address of broadcaster【后续建立点对点连接,监听CONNECT_REQ请求】AdvData0SEO靠我~31Broadcast dataADV_NONCONN_IND同ADV_IND,不可连接不可扫描AdvA6address of broadcaster【用于定时传输简单数据】AdvData0~31BSEO靠我roadcast dataADV_SCAN_IND同ADV_IND,不可连接可扫描AdvA6address of broadcaster【用于传输额外数据,监听SCAN_REQ请求】AdvData0~SEO靠我31Broadcast dataADV_DIRECT_IND点对点连接,已知双方蓝牙地址,无广播数据,可被指定设备连接不可扫描AdvA6address of broadcaster【快速建立连接,不关SEO靠我心广播数据,监听CONNECT_REQ请求】InitA6address of receiver/initiaterScanningSCAN_REQ接收ADV_IND/ADV_SCAN_IND后,请求更SEO靠我多信息ScanA6address of scanne r【接收广播数据后请求更多信息】AdvA6address of broadcasterSCAN_RSPSCAN_REQ的响应,返回更多信息AdvASEO靠我6address of broadcasterScanRspData0~31response dataInitiatingCONNECT_REQ接收ADV_IND/ADV_DIRECT_IND后,请求SEO靠我建立连接InitA6address of receiver/initiater【请求建立连接】AdvA6address of broadcasterLLData22parameters of connSEO靠我ectionBLE设备地址类型
Static Device Address:上电生成,46-bit的random+11,断电后可变
Private Device Address:进一步提供定时更新和地址加SEO靠我密提高可靠行和安全性
Non-resolvable Private Address:按周期定时更新,46-bit的random+00
Resolvable Private Address:通过随机数和IRSEO靠我K(Identity Resolving Key)生成,24-bit的hash+22-bit的random+10
Public Device Address:IEEE分配,24-bit的company_SEO靠我id+24-bit的company_assigned,类似MAC
Random Device Address:随机生成,解决费用和维护、设备身份绑定的问题
HCI
将Link Layer提供的功能封装成CoSEO靠我mmand/Event组
Command格式
commandOCF(Opcode Command Field)表示特定的HCI命令,OGF(Opcode Group Field)表示该HCI命令所属组别,SEO靠我他们共同组成16位操作码;Parameter Total Length表示所有参数总长度
所有BLE相关的HCI Command的OGF都是0x08
Event格式
event这些Command/EventSEO靠我包括广播、扫描、连接建立的相关操作,这些都可以通过hcitool命令进行测试
GAP
会从应用程序角度对各种状态和操作再一次进行封装,包括设备角色,通信的模式和操作的定义
与GAP广播通信相关的是广播和发现SEO靠我模式
Broadcast mode and observation procedure,广播模式及对应的解析过程,对应状态下的角色双方就是Broadcaster和Observer
Discovery moSEO靠我des and procedures,发现模式及对应的发现过程,对应的角色就是Peripheral和Central
广播数据格式
adv_data广播/扫描应答数据,包含有意义部分和无意义部分(补齐为0)SEO靠我,有意义部分是由一个个广播块(AD Structure)组成,每个广播块包含1字节长度(指示数据部分长度)和剩下的数据部分,数据部分又分为数据类型和数据内容,数据类型会指示真实Data部分的内容,例如SEO靠我0x01表示Data内容是描述设备物理连接状态,再例如0x08表示Data内容是设备名称,更多可以参考generic-access-profile
举个广播数据的例子
02 01 06 03 03 aa SEO靠我fe 17 16 aa fe 00 -10 00 01 02 03 04 05 06 07 08 09 0a 0b 0e 0f 00 00 00 00 102 01 06是一个AD SSEO靠我tructure,数据部分长度为2字节,类型是0x01,描述设备物理连接状态,数据部分0x06,1字节8bit,每bit都是一个标志位([预留]|[预留]|[预留]|[同时支持BLE和BR/EDR(HSEO靠我ost)]|[同时支持BLE和BR/EDR(Controller)]|[不支持BR/EDR]|[普通发现模式]|[有限发现模式]),那么这个广播就是普通发现模式,不支持BR/EDR
03 03 aa fSEO靠我e是第二个AD Structure,数据部分长度为3字节,类型是0x03,表示16-bits的Service UUID
17 16 aa fe 00 -10 00 01 02 03 04 05 06 0SEO靠我7 08 09 0a 0b 0e 0f 00 00 00 00是最后一个AD Structure,数据部分长度为0x17即23字节,类型是0x16,表示服务数据
AD TypeDescriptionADSEO靠我 Data0x01设备物理连接状态1字节8bit,每个bit都是一个标志位 [预留]|[预留]|[预留]|[同时支持BLE和BR/EDR(Host)]|[同时支持BLE和BR/EDR(ControllSEO靠我er)]|[不支持BR/EDR]|[普通发现模式]|[有限发现模式]0x02UUID非完整的16-bit UUID0x03UUID完整的16-bit UUID0x04UUID非完整的32-bit UUSEO靠我ID0x05UUID完整的32-bit UUID0x06UUID非完整的128-bit UUID0x07UUID完整的128-bit UUID0x08设备名称缩写设备名称0x09设备名称完整设备名称0SEO靠我x0aTX Power LevelTX Power Level0xff厂商数据[厂商ID]|[厂商自定义数据]经典蓝牙中保持连接非常耗费资源,但是每次连接建立效率又非常低,SEO靠我为了优化体验,BLE简化了连接过程(毫秒级),极大的降低了面向连接通信的代价
蓝牙通信系统中,对于连接的定义是:在约定的时间段内,双方都到一个指定的物理Channel上通信。
LL
角色定义。BLE为处于连SEO靠我接状态的两个设备定义了两个角色,Master和Slave。Master作为连接发起方,定义和连接相关的参数,Slave是连接的接收方,请求连接参数
PDU(Packet Data Unit)格式
DC_PSEO靠我DU面向连接的通信使用特定的PDU,称为Data channel PDU
LLID指示Data Channel传输的PDU类型,传输数据是LL Data PDU,传输控制信息是LL Control PDSEO靠我U,NESN(Next Expected Sequence Number)和SN(Sequence Number)用于数据传输过程中的应答和流控,MD(More Data)用于关闭连接,RFU是预留位SEO靠我,Length指示有效数据长度,包括Payload和MIC
LLIDtypeDescription01bLL Data PDU空包或未传输完成的消息(被拆包)10bLL Data PDU(不需拆包)完整SEO靠我消息或第一个包11bLL Control PDU用于控制、管理LL连接的数据包,此时Payload为1字节Opcode和剩余的控制数据建立连接
可连接状态的设备(Advertiser)按照一定周期广播可SEO靠我连接数据包
主动连接的设备(Initiator)收到广播包后回应一个连接请求(约定时间点、物理信道等)
Initiator发送完毕后进入连接状态,成为Master
Advertiser接收到连接请求后也进入SEO靠我连接状态,成为Slave
双方按照参数约定,定时切换到某一物理信道,开始依次收发数据,直至连接断开
HCI
封装Link Layer的功能,主要包括连接的建立、关闭、参数设置和管理,以及数据封装和转发
GAPSEO靠我
定义设备具有的能力和操作
White List
白名单就是一组蓝牙地址列表,通过设置白名单可以允许扫描、连接特定的蓝牙设备,以及被扫描、连接
LL Privacy
在白名单的基础SEO靠我上将设备地址进行加密,转变成Resolvable Private addresses
本地Resolving List需要保存每一对BLE设备的key/address信息,格式为:Local IRK|PSEO靠我eer IRK|Peer Device Identity Address|Address Type
LL Privacy机制在Controller中完成,即加解密操作对HCI上层是透明的
LL EncrySEO靠我ption
数据发送和接收过程进行加解密
加解密操作在Link Layer完成
Host会保存至少1280bit的LTK(根密钥),启动加密传输时,LL会首先协商出一个128-bit的随机数SDK(SessSEO靠我ion Key Diversifier)和64-bit的iv,经过Encryption Engine和LTK生成此次通信的会话密钥SessionKey
SecurityManager
为BLE设备提供加密SEO靠我连接相关的key,包含以下规范:
定义了配对(Pairing)的相关概念及过程
定义了密码工具箱,包含配对、加密所需的各种算法
定义了安全管理协议(Security Manager Protocol,SMPSEO靠我),实现配对、密码传输等操作
SMP规范中,配对的定义是,Master和Slave通过协商确定用于加密通信的key的过程,包含三个阶段:
阶段一、Pairing Feature Exchange,交换双方SEO靠我配对相关的设置,如配对方法、鉴权方式等
鉴权方式可以采用交换额外的信息(Out of band,OOB),或者弹窗引入人来判定,称为MITM鉴权,还有输入配对码进行对比(Passkey Entry/NuSEO靠我meric Comparison),或者不鉴权(Just Work)
阶段二、通过SMP协议进行配对操作,有两种配对方案,LE Legacy Pairing和LE Secure Connections
阶SEO靠我段三、协商好密钥key,建立加密连接,传输密钥信息(可选)
具体可以参考开发的蓝牙测试工具:BLETool
BLE,蓝牙低功耗(极低的运行和待机功耗)
AndroSEO靠我id 4.3(API 18) 开始引入 BLE ,即蓝牙4.0
Android 4.3 的 BLE 只支持 Central Role(中心设备,扫描并连接外围设备)
Android 5.0 开始同时支持 SEO靠我Central Role 和 Peripheral Role(外围设备,向外广播发送数据)
1、权限
<!--蓝牙权限--> <uses-permission android:name="SEO靠我android.permission.BLUETOOTH"/> <!--蓝牙相关操作设置权限--> <uses-permission android:name="andSEO靠我roid.permission.BLUETOOTH_ADMIN"/> <!--位置权限,扫描时需要,Android 9-需要模糊定位,Android 10开始需要精确定位--> SEO靠我 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permiSEO靠我ssion android:name="android.permission.ACCESS_FINE_LOCATION"/> <!--声明使用BLE硬件特性,仅系统支持时可安装--> SEO靠我 <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />2、开启/关闭SEO靠我蓝牙
// 判断支持蓝牙功能 if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))SEO靠我 {// 获取蓝牙管理服务BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOSEO靠我TH_SERVICE);// 获取蓝牙适配器BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();if (bluetootSEO靠我hAdapter != null) {// 判断蓝牙是否开启if (bluetoothAdapter.isEnabled()) {// 关闭蓝牙bluetoothAdapter.disable();}SEO靠我 else {// 1、静默开启蓝牙bluetoothAdapter.enable();// 2、显式请求开启蓝牙Intent intent = new Intent(BluetoothAdapterSEO靠我.ACTION_REQUEST_ENABLE);startActivityForResult(intent, REQUEST_BLUETOOTH_ENABLE);}} }3、扫描与监听SEO靠我
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {// 获取蓝牙管理服务BluetoothSEO靠我Manager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);// 获取蓝牙适配器SEO靠我BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();if (bluetoothAdapter != null) {// SEO靠我判断蓝牙是否开启if (bluetoothAdapter.isEnabled()) {// 获取扫描器实例BluetoothLeScanner bluetoothLeScanner = bluetooSEO靠我thAdapter.getBluetoothLeScanner();boolean isScanning = false;if (bluetoothLeScanner != null) {if (isSEO靠我Scanning) {// 停止扫描isScanning = false;bluetoothLeScanner.stopScan(scanCallback);} else {// 开始扫描isScanSEO靠我ning = true;bluetoothLeScanner.startScan(scanCallback);}}}} }Android 8 开始提供一个后台持续扫描的API,应用杀死SEO靠我后也可以继续扫描,直到关闭蓝牙【待验证】
public int startScan (List<ScanFilter> filters, ScanSettings settings, PendingInSEO靠我tent callbackIntent);// 设置拦截器和扫描选项 bluetoothLeScanner.startScan(scanFilters, scanSettings, sSEO靠我canCallback);初始化扫描过滤器
scanFilters = new ArrayList<>(); ScanFilter scanFilter = new ScanFilterSEO靠我.Builder().setDeviceName("lalala").setServiceUuid(new ParcelUuid(UUID.randomUUID())).build(); SEO靠我 scanFilters.add(scanFilter);初始化扫描设置
scanSettings = new ScanSettings.Builder()// 设置扫描模式.setScanMode(SEO靠我ScanSettings.SCAN_MODE_BALANCED)// 设置回调类型 .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)/SEO靠我/ 设置配对模式.setMatchMode(ScanSettings.MATCH_MODE_STICKY)// 设置报告延迟.setReportDelay(0).build();两个类都是通过 BuiSEO靠我lder 构造,提供系列函数用于参数设置,如 setDeviceName()、setScanMode()、setMatchMode() 等
4、扫描回调
scanCallback = new ScanCaSEO靠我llback {@Overridepublic void onScanResult(int callbackType, ScanResult result) {super.onScanResult(cSEO靠我allbackType, result);}@Overridepublic void onScanFailed(int errorCode) {super.onScanFailed(errorCodeSEO靠我);}@Overridepublic void onBatchScanResults(List<ScanResult> results) {super.onBatchScanResults(resulSEO靠我ts);} }其中 onBatchScanResults() 是批量返回扫描结果。可通过下面的接口判断蓝牙芯片是否支持批处理
Bluetoothadapter.isOffloadedScSEO靠我anBatchingSupported(); 1注意 onScanResult() 和 onBatchScanResults() 是互斥的,ScanSettings 中 setRepoSEO靠我rtDelay() 设置为0(默认)则通过 onScanResult() 返回扫描结果,否则开启批处理扫描模式,并触发 onBatchScanResults() 回调。
5、广播数据解析
扫描成功会返回 SEO靠我ScanResult 广播数据类,然后进一步解析
// 返回远程设备类 BluetoothDevice device = scanResult.getDevice(); SEO靠我// 返回扫描记录,包含广播和扫描响应 ScanRecord scanRecord = scanResult.getScanRecord(); // 返回信号强度,[-SEO靠我127, 126] int rssi = scanResult.getRssi()BluetoothDevice 是设备信息类,常用的方法有
// 获取硬件地址 StriSEO靠我ng address = device.getAddress(); // 获取蓝牙名称 String name = device.getName(); SEO靠我// 获取设备类型,如DEVICE_TYPE_CLASSIC、DEVICE_TYPE_LE、DEVICE_TYPE_DUAL、DEVICE_TYPE_UNKNOWN int type SEO靠我= device.getType(); // 获取绑定状态,如BOND_NONE、BOND_BONDING、BOND_BONDED int state = deviceSEO靠我.getBondState();6、连接设备
扫描返回的广播消息中可以获取到远程设备的MAC地址,可用于设备的连接
if (bluetoothAdapter.isEnabled()) {// 获取远程设备SEO靠我对象BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);if (bluetoothDevice !=SEO靠我 null) {handler.post(new Runnable() {@Overridepublic void run() {BluetoothGatt bluetoothGatt;if (BuiSEO靠我ld.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 连接远程设备bluetoothGatt = bluetoothDevice.connectGatt(cSEO靠我ontext, false, bluetoothGattCallback, BluetoothDevice.TRANSPORT_LE);} else {bluetoothGatt = bluetootSEO靠我hDevice.connectGatt(context, false, bluetoothGattCallback);}}});} }bluetoothGatt 是蓝牙通用属性协议的封SEO靠我装,定义了BLE通信的一些基本规则和连接通信操作
7、连接回调
bluetoothGattCallback 则是 bluetoothGatt 连接的回调类,通知客户端连接状态和结果
bluetoothGatSEO靠我tCallback = new BluetoothGattCallback {@Overridepublic void onConnectionStateChange(BluetoothGatt gaSEO靠我tt, final int status, final int newState) {super.onConnectionStateChange(gatt, status, newState);}@OSEO靠我verridepublic void onServicesDiscovered(BluetoothGatt gatt, final int status) {super.onServicesDiscoSEO靠我vered(gatt, status);}@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt, final BluetootSEO靠我hGattCharacteristic characteristic) {super.onCharacteristicChanged(gatt, characteristic);}@OverridepSEO靠我ublic void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristicSEO靠我, final int status) {super.onCharacteristicRead(gatt, characteristic, status);}@Overridepublic void SEO靠我onCharacteristicWrite(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final inSEO靠我t status) {super.onCharacteristicWrite(gatt, characteristic, status);}@Overridepublic void onDescripSEO靠我torRead(BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {super.onDesSEO靠我criptorRead(gatt, descriptor, status);}@Overridepublic void onDescriptorWrite(BluetoothGatt gatt, fiSEO靠我nal BluetoothGattDescriptor descriptor, final int status) {super.onDescriptorWrite(gatt, descriptor,SEO靠我 status);} }连接成功及其他连接状态改变都会调用 onConnectionStateChange() 方法。status 表示这个操作的状态,是 BluetoothGatt.SEO靠我GATT_SUCCESS 或者读写受限、超过范围等其他错误状态。newState 则表示当前设备的连接状态,连接成功为 BluetoothProfile.STATE_CONNECTED,连接失败是BlSEO靠我uetoothProfile.STATE_DISCONNECTED。
8、发现服务
连接成功后就可以开始通信,从请求服务开始(Profile只是一系列具有共同业务需求的服务的抽象集合,服务才是实体)
bluSEO靠我etoothGatt.discoverServices(); 1发现服务后会触发 onServicesDiscovered() 回调,然后继续获取服务
// 获取所有服务 SEO靠我 List<BluetoothGattService> bleServiceList = bluetoothGatt.getServices(); // 通过uuid获取特定的服务 SEO靠我 BluetoothGattService bleService = bluetoothGatt.getService(serviceUuid);BluetoothGattService SEO靠我是蓝牙服务类,是与某个场景相关的一系列行为的抽象,具有一个唯一的UUID,然后服务类型,如SERVICE_TYPE_PRIMARY、SERVICE_TYPE_SECONDARY(主要服务可以包含二级服SEO靠我务),包含的特征列表
9、获取特征
// 获取所有特征 List<BluetoothGattCharacteristic> bleCharacteristicList = bleServiSEO靠我ce.getCharacteristics(); // 通过uuid获取特定的特征 BluetoothGattCharacteristic bleCharacterisSEO靠我tic = bleService.getCharacteristic(characteristicUuid);BluetoothGattCharacteristic 是蓝牙特征类,是通信的基本数据单位SEO靠我,包含标志特征的唯一的UUID,描述特征访问权限的特性,如PROPERTY_BROADCAST、PROPERTY_READ、PROPERTY_WRITE等,特征的实际取值,以及特征的描述
10、读写特征SEO靠我
// 读取特征 bluetoothGatt.readCharacteristic(bleCharacteristic); // 设置并写入特征 bleCSEO靠我haracteristic.setValue("XXX"); bluetoothGatt.writeCharacteristic(bleCharacteristic);这里的读写特征函SEO靠我数都是返回布尔类型表示是否操作成功,如果成功真正的值会在 onCharacteristicRead/onCharacteristicWrite 回调中读取/写入
@Override puSEO靠我blic void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int sSEO靠我tatus) {switch (status) {case GATT_SUCCESS:String valueStr = BLEUtils.byte2HexString(characteristic.SEO靠我getValue());break;case GATT_READ_NOT_PERMITTED:ToastUtils.showShort(context, "GATT_READ_NOT_PERMITTESEO靠我D");break;default:ToastUtils.showShort(context, "CHARACTERISTIC_READ_FAILED");break;} }@OverSEO靠我ride public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic charaSEO靠我cteristic, int status) {switch (status) {case GATT_SUCCESS:String valueStr = BLEUtils.byte2HexStringSEO靠我(characteristic.getValue());break;default:ToastUtils.showShort(context, "CHARACTERISTIC_WRITE_FAILEDSEO靠我");break;} }11、监听特征
真正要实现通信除了单方面读写,还需要对数据变化进行监听,这样就可以进行数据交换
// 设置特征监听为true,且要求特征具有NOTIFY属性 SEO靠我 bluetoothGatt.setCharacteristicNotification(characteristic, true);这样,自己或对方特征改变时就会回调函数从而获取改变后的特征SEO靠我值
@Override public void onCharacteristicChanged(BluetoothGattCharacteristic characteristic) {SEO靠我Log.d("bledemo", "uuid = " + characteristic.getUuid().toString());Log.d("bledemo", "value = 0x" + BLSEO靠我EUtils.byte2HexString(characteristic.getValue())); }12、获取描述
// 获取所有描述 List<BluetoothGSEO靠我attDescriptor> bleDescriptorList = bleCharacteristic.getDescriptors(); // 通过uuid获取特定的描述 SEO靠我 BluetoothGattDescriptor bleDescriptor = bleCharacteristic.getDescriptor(descriptorUuid);BluetootSEO靠我hGattDescriptor 是蓝牙特征描述类,包含对特征的一些额外描述信息
13、读写描述
// 读取描述 bluetoothGatt.readDescriptor(bleDescriSEO靠我ptor); // 设置并写入描述 bleDescriptor.setValue("XXX"); bluetoothGatt.writeDescriptSEO靠我or(bleDescriptor);同样读写成功会触发onDescriptorRead/onDescriptorWrite 回调
@Override public void onDescSEO靠我riptorRead(BluetoothGattDescriptor descriptor, int status) {switch (status) {case GATT_SUCCESS:StrinSEO靠我g valueStr = BLEUtils.byte2HexString(descriptor.getValue());break;case GATT_READ_NOT_PERMITTED:ToastSEO靠我Utils.showShort(context, "GATT_READ_NOT_PERMITTED");break;default:ToastUtils.showShort(context, "DESSEO靠我CRIPTOR_READ_FAILED");break;} }@Override public void onDescriptorWrite(BluetoothGattSEO靠我Descriptor descriptor, int status) {switch (status) {case GATT_SUCCESS:String valueStr = BLEUtils.bySEO靠我te2HexString(descriptor.getValue());break;default:ToastUtils.showShort(context, "DESCRIPTOR_WRITE_FASEO靠我ILED");break;} }14、断开连接
// 断开连接,会触发onConnectionStateChange()回调 bluetoothGatt.disconneSEO靠我ct(); // 关闭连接,不会触发回调 bluetoothGatt.close(); 123415、开启/关闭广播
if (bluetoothAdaptSEO靠我er.isEnabled()) {// 设置广播设备的名称,方便搜索bluetoothAdapter.setName("XXX");// 获取广播类BluetoothLeAdvertiser blueSEO靠我toothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();if (bluetoothLeAdvertiser != null) {SEO靠我if (isAdvertising) {// 关闭广播bluetoothLeAdvertiser.stopAdvertising(advertiseCallback);} else {// 开始广播bSEO靠我luetoothLeAdvertiser.startAdvertising(advertiseSetting, advertiseData, advertiseCallback);}} SEO靠我 }还可以发送带响应报文的广播包
bluetoothLeAdvertiser.startAdvertising(advertiseSetting, advertiseData, advertiseResSEO靠我Data, advertiseCallback); 1其中 advertiseSetting 为广播设置类对象
advertiseSetting = new AdvertiseSettiSEO靠我ngs.Builder()// 广播模式,控制广播功率和延迟.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)// 广播发射SEO靠我功率级别.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)// 广播超时时间,最大值为 3*60*1000 毫秒,为 0 时禁用超时SEO靠我,默认无限广播.setTimeout(advertiseTimeout)// 广播连接类型.setConnectable(true).build(); 12345678910adverSEO靠我tiseData、advertiseResData 为广播包
advertiseData = new AdvertiseData.Builder()// 广播是否包含设备名称.setIncludeDevSEO靠我iceName(true)// 广播是否包含发射功率.setIncludeTxPowerLevel(true)// 添加服务uuid.addServiceUuid(new ParcelUuid(UUISEO靠我D.randomUUID())).build();advertiseResData = new AdvertiseData.Builder()// 添加自定义服务数据.addServiceData(nSEO靠我ew ParcelUuid(UUID.randomUUID()), new byte[]{1,2,3,4})// 添加自定义厂商数据.addManufacturerData(0x06, new bytSEO靠我e[]{5,6,7,8}).build();16、广播回调
advertiseCallback 是广播回调
advertiseCallback = new AdvertiseCallback {@OverSEO靠我ridepublic void onStartSuccess(AdvertiseSettings settingsInEffect) {super.onStartSuccess(settingsInESEO靠我ffect);}@Overridepublic void onStartFailure(int errorCode) {super.onStartFailure(errorCode);} SEO靠我 }17、启动GATT服务
只有广播仍然不够,作为外围角色的设备还需要启动GATT服务,等待中心设备与之建立连接之后就可以通过服务通信
// 启动 Gatt 服务 bluetoothGaSEO靠我ttServer = bluetoothManager.openGattServer(context, bluetoothGattServerCallback); 12接下来可以向启动SEO靠我的GATT服务中添加Service
// 构造服务 BluetoothGattService bluetoothGattService = new BluetoothGattServicSEO靠我e(UUID.randomUUID(), BluetoothGattService.SERVICE_TYPE_PRIMARY);// 构造特征 BluetoothGattCharactSEO靠我eristic bluetoothGattCharacteristic = new BluetoothGattCharacteristic(UUID.randomUUID(), BluetoothGaSEO靠我ttCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharactSEO靠我eristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.SEO靠我PERMISSION_READ); bluetoothGattCharacteristic.setValue("character_test_value");// 构造描述 SEO靠我 BluetoothGattDescriptor bluetoothGattDescriptor = new BluetoothGattDescriptor(UUID.randomUUID(), SEO靠我BluetoothGattDescriptor.PERMISSION_READ |BluetoothGattDescriptor.PERMISSION_WRITE); bluetootSEO靠我hGattDescriptor.setValue("descriptor_test_value".getBytes());// 添加描述到特征中 bluetoothGattCharacSEO靠我teristic.addDescriptor(bluetoothGattDescriptor); // 添加特征到服务中 bluetoothGattService.adSEO靠我dCharacteristic(bluetoothGattCharacteristic); // 添加服务 bluetoothGattServer.addServiceSEO靠我(bluetoothGattService);18、GATT服务回调
bluetoothGattServerCallback 是GATT服务的回调,当设备被连接、通信(读写特征)时都会触发响应的回调函数SEO靠我
bluetoothGattServerCallback = new BluetoothGattServerCallback {@Overridepublic void onConnectionStatSEO靠我eChange(BluetoothDevice device, int status, int newState) {super.onConnectionStateChange(device, staSEO靠我tus, newState);}@Overridepublic void onServiceAdded(int status, BluetoothGattService service) {superSEO靠我.onServiceAdded(status, service);}@Overridepublic void onCharacteristicReadRequest(BluetoothDevice dSEO靠我evice, int requestId, int offset,BluetoothGattCharacteristic characteristic) {super.onCharacteristicSEO靠我ReadRequest(device, requestId, offset, characteristic);}@Overridepublic void onCharacteristicWriteReSEO靠我quest(BluetoothDevice device, int requestId,BluetoothGattCharacteristic characteristic,boolean prepaSEO靠我redWrite, boolean responseNeeded,int offset, byte[] value) {super.onCharacteristicWriteRequest(devicSEO靠我e, requestId, characteristic, preparedWrite,responseNeeded, offset, value);}@Overridepublic void onDSEO靠我escriptorReadRequest(BluetoothDevice device, int requestId,int offset, BluetoothGattDescriptor descrSEO靠我iptor) {super.onDescriptorReadRequest(device, requestId, offset, descriptor);}@Overridepublic void oSEO靠我nDescriptorWriteRequest(BluetoothDevice device, int requestId,BluetoothGattDescriptor descriptor,booSEO靠我lean preparedWrite, boolean responseNeeded,int offset, byte[] value) {super.onDescriptorWriteRequestSEO靠我(device, requestId, descriptor, preparedWrite, responseNeeded,offset, value);}@Overridepublic void oSEO靠我nNotificationSent(BluetoothDevice device, int status) {super.onNotificationSent(device, status);}免责声SEO靠我明:本文素材来源网络,版权归原作者所有。如涉及作品版权问题,请与我联系删除。
‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧ END ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧
关注我的微信公众号,回复“加群”按规则加入技术交SEO靠我流群。 点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。网站备案号:浙ICP备17034767号-2