解决方案

蓝牙BLE协议分析【附代码实例】

seo靠我 2023-09-25 00:24:46

    关注、星标公众号,直达精彩内容

来源:https://blog.csdn.net/weixin_35016347/article/details/108011878

整理:技术让梦想更伟大 | 李肖遥

PaSEO靠我rt101 蓝牙概述

蓝牙技术起源于爱立信在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版本,方便入门,可以理解很多核心协议的设计思想

Part202 蓝牙技术分类

蓝牙技术包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)

蓝牙低功耗,则不关心传输速率,而是从降低功耗的角度实现的另一套技术,跟前面的协议没有丝毫关系

Part303 蓝牙架构

structure

蓝牙协议将蓝牙整体分成了两层架构,底层是核心协议,描SEO靠我述了蓝牙核心技术的基础和规范,应用层协议则基于具体需求,使用核心协议提供的机制,实现不同的功能策略

核心协议包含两部分,Host和Controller,这两部分在不同的蓝牙协议版本中略有区别,但大致上是SEO靠我,Controller完成硬件侧的规范制订,包括信号调制解调,会抽象出用于通信的逻辑链路,可能存在一个或多个,如LE Controller、BR/EDR Controller;Host则在逻辑链路的基SEO靠我础上完成更友好的封装,屏蔽掉技术细节,方便应用层对数据的使用

Part404 蓝牙协议

蓝牙协议也采用层次结构,自下而上依次为物理层、逻辑层、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带宽;有两种物理信道,每次只能在一种物理信道上通信,采用时分方式

PaSEO靠我rt505 BLE协议栈

实现一个BLE应用,需要一个支持BLE射频的芯片,然后基于一个与芯片配套的协议栈,开发蓝牙应用。

协议栈的作用就是软件和硬件之间的桥梁,对应用数据进行封包然后生成可以通过射频发送SEO靠我的空中数据包及其逆向过程。

BLE-protocol

Physical 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靠我ierarchy

Profile位于最顶层,不是真正存在的配置文件,而是一个或多个场景相关的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:实现蓝牙设备的配对操作

Part606 BLE的广播

使用场景

单向、无连接的数据通信,发送者使用广播信道发送数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)格式

pdu

Type是指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靠我ection

BLE设备地址类型

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格式

command

OCF(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 1

02 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]|[厂商自定义数据]

Part707 BLE的连接

经典蓝牙中保持连接非常耗费资源,但是每次连接建立效率又非常低,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靠我

定义设备具有的能力和操作

Part808 BLE安全机制

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,建立加密连接,传输密钥信息(可选)

Part909 Android中的BLE应用

具体可以参考开发的蓝牙测试工具: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(); 1234

15、开启/关闭广播

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(); 12345678910

adverSEO靠我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靠我流群。 点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看。
“SEO靠我”的新闻页面文章、图片、音频、视频等稿件均为自媒体人、第三方机构发布或转载。如稿件涉及版权等问题,请与 我们联系删除或处理,客服邮箱:html5sh@163.com,稿件内容仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同 其观点或证实其内容的真实性。

网站备案号:浙ICP备17034767号-2