蓝牙低功耗BLE调研与开发
一: 蓝牙简介
蓝牙是一种近距离无线通讯技能,运转在2.4GHz免费频段,它的特性便是近距离通讯,典型距离是 10 米以内,传输速度最高可达 24 Mbps,支撑多衔接,安全性高,十分适合用智能设备上。
1. 蓝牙技能的开展
1999年 蓝牙1.0:
前期的蓝牙 1.0 A 和 1.0B 版存在多个问题,有多家厂商指出他们的产品互不兼容。
一起,在两个设备“链接”(Handshaking)的进程中,蓝牙硬件的地址(BD_ADDR)会被发送出去,在协议的层面上不能做到匿名,造成泄漏数据的危险。
因而,当 1.0 版别推出今后,蓝牙并未当即遭到广泛的运用。除了其时对应蓝牙功用的电子设备种类少,蓝牙设备也十分昂贵。
2001年 蓝牙1.1版别:
蓝牙 1.1 版正式列入 IEEE 802.15.1 规范,该规范界说了物理层(PHY)和媒体拜访操控(MAC)规范,用于设备间的无线衔接,传输率为 0.7Mbps。但由于是前期规划,简略遭到同频率之间产品干扰,影响通讯质量。
2003年:蓝牙 1.2
蓝牙 1.2 版针对 1.0 版别露出出的安全性问题,完善了匿名办法,新增屏蔽设备的硬件地址(BD_ADDR)功用,维护用户免受身份嗅探进犯和跟踪,一起向下兼容 1.1 版。此外,还增加了四项新功用:
(1)AFH(Adaptive Frequency Hopping)适应性跳频技能,削减了蓝牙产品与其它无线通讯设备之间所发生的干扰问题;
(2)eSCO(Extended Synchronous Connection-Oriented links)延伸同步连结导向信道技能,用于供给 QoS 的音频传输,进一步满足高阶语音与音频产品的需求;
(3)Faster Connection 快速衔接功用,能够缩短从头查找与再衔接的时刻,使衔接进程更为稳定快速;
(4)支撑 Stereo 音效的传输要求,但只能以单工办法作业。
2004年:蓝牙 2.0
蓝牙 2.0 是 1.2 版别的改良版,新增的 EDR(Enhanced Data Rate)技能经过进步多使命处理和多种蓝牙设备一起运转的才能,
使得蓝牙设备的传输率可达 3Mbps。
蓝牙 2.0 支撑双工办法:能够一边进行语音通讯,一边传输文档/高质素图片。
一起,EDR 技能经过削减作业负债循环来下降功耗,由于带宽的增加,蓝牙 2.0 增加了衔接设备的数量。
2007年:蓝牙 2.1
蓝牙 2.1 新增了 Sniff Subrating 省电功用,将设备间彼此承认的讯号发送时刻距离从旧版的 0.1 秒延长到 0.5 秒左右,然后让蓝牙芯片的作业负载大幅下降。
别的,新增 SSP 简易安全配对功用,改善了蓝牙设备的配对体验,一起进步了运用和安全强度。
支撑 NFC 近场通讯,只要将两个内置有 NFC 芯片的蓝牙设备彼此靠近,配对密码将经过 NFC 进行传输,无需手动输入。
2009 年:蓝牙 3.0
蓝牙 3.0 新增了可选技能 High Speed,High Speed 能够使蓝牙调用 802.11 WiFi 用于完结高速数据传输,传输率高达 24Mbps,是蓝牙 2.0 的 8 倍,轻松完结录像机至高清电视、PC 至 PMP、UMPC 至打印机之间的材料传输。
蓝牙 3.0 的中心是 AMP(Generic Alternate MAC/PHY),这是一种全新的替换射频技能,答应蓝牙协议栈针对任一使命动态地挑选正确射频。
功耗方面,蓝牙 3.0 引进了 EPC 增强电源操控技能,再辅以 802.11,实践闲暇功耗明显下降。
此外,新的规范还参加 UCD 单向播送无衔接数据技能,进步了蓝牙设备的相应才能。
2010 年:蓝牙 4.0
蓝牙 4.0 是迄今为止第一个蓝牙归纳协议规范,将三种规格集成在一起。其间最重要的改变便是 BLE(Bluetooth Low Energy)低功耗功用,提出了低功耗蓝牙、传统蓝牙和高速蓝牙三种办法:
”高速蓝牙“主攻数据交换与传输;“传统蓝牙”则以信息沟通、设备衔接为要点;”低功耗蓝牙“以不需占用太多带宽的设备衔接为主,功耗较老版别下降了 90%。
BLE 前身是 NOKIA 开发的 Wibree 技能,本是作为一项专为移动设备开发的极低功耗的移动无线通讯技能,在被 SIG 接纳并规范化之后重命名为 Bluetooth Low Energy(后简称低功耗蓝牙)。这三种协议规范还能够彼此组合搭配、然后完结更广泛的运用办法。
蓝牙 4.0 的芯片办法分为 Single mode 与 Dual mode。Single mode 只能与蓝牙 4.0 彼此传输无法向下与 3.0/2.1/2.0 版别兼容;Dual mode 能够向下兼容 3.0/2.1/2.0 版别。前者运用于运用纽扣电池的传感器设备,例如对功耗要求较高的心率检测器和温度计;后者运用于传统蓝牙设备,一起兼顾低功耗的需求。
此外,蓝牙 4.0 还把蓝牙的传输距离进步到100米以上(低功耗办法条件下)。具有更快的呼应速度,最短可在 3 毫秒内完结衔接设置并开端传输数据。更安全的技能,运用 AES-128 CCM 加密算法进行数据包加密和认证。
2013 年:蓝牙 4.1
蓝牙 4.1 在传输速度和传输规模上改变很小,但在软件方面有着明显的改善。此次更新目的是为了让 Bluetooth Smart 技能终究成为物联网(Internet of Things)开展的中心动力。
支撑与 LTE 无缝协作。当蓝牙与 LTE 无线电信号一起传输数据时,那么蓝牙 4.1 能够主动和谐两者的传输信息,以确保协同传输,下降彼此干扰。
答应开发人员和制造商「自界说」蓝牙 4.1 设备的从头衔接距离,为开发人员供给了更高的灵活性和掌控度。
支撑「云同步」。蓝牙 4.1 参加了专用的 IPv6 通道,蓝牙 4.1 设备只需求衔接到能够联网的设备(如手机),就能够经过 IPv6 与云端的数据进行同步,满足物联网的运用需求。
支撑「扩展设备」与「中心设备」人物交换。支撑蓝牙 4.1 规范的耳机、手表、键鼠,能够不必经过 PC、平板、手机等数据纽带,完结自主收发数据。例如智能手表和计步器能够绕过智能手机,直接完结对话。
2014 年:蓝牙 4.2
蓝牙 4.2 的传输速度更加快速,比上代进步了 2.5 倍,由于蓝牙智能(Bluetooth Smart)数据包的容量进步,其可容纳的数据量相当于此前的10倍左右。
改善了传输速率和隐私维护程度,蓝牙信号想要衔接或许追踪用户设备,有必要经过用户许可。用户能够放心运用可穿戴设备而不必担心被跟踪。
支撑 6LoWPAN,6LoWPAN 是一种依据 IPv6 的低速无线个域网规范。蓝牙 4.2 设备能够直接经过 IPv6 和 6LoWPAN 接入互联网。这一技能答应多个蓝牙设备经过一个终端接入互联网或许局域网,这样,大部分智能家居产品能够扔掉相对杂乱的 WiFi 衔接,改用蓝牙传输,让个人传感器和家庭间的互联更加快捷快速。
2016 年:蓝牙 5.0
蓝牙 5.0 在低功耗办法下具有更快更远的传输才能,传输速率是蓝牙 4.2 的两倍(速度上限为 2Mbps),有用传输距离是蓝牙 4.2 的四倍(理论上可达 300 米),数据包容量是蓝牙 4.2 的八倍。
支撑室内定位导航功用,结合 WiFi 能够完结精度小于 1 米的室内定位。
针对 IoT 物联网进行底层优化,力求以更低的功耗和更高的性能为智能家居服务。
- 蓝牙版别总结
- 2007年发布的2.1版别,是之前运用最广的,也是咱们所谓的经典蓝牙。
- 2009年推出蓝牙 3.0版别,也便是所谓的高速蓝牙,传输速率理论上可高达24 Mbit/s;
- 2010年推出蓝牙4.0版别,它是相对之前版别的集大成者,它包括经典蓝牙、高速蓝牙和蓝牙低功耗协议。
经典蓝牙包括旧有蓝牙协议,高速蓝牙依据Wi-Fi,低功耗蓝牙便是BLE。 - 2016年蓝牙技能联盟提出了新的蓝牙技能规范,即蓝牙5.0版别。
蓝牙5.0针对低功耗设备速度有相应进步和优化,结合wifi对室内方位进行辅佐定位,
进步传输速度,增加有用作业距离,首要是针对物联网方向的改善。
2. Android版别中蓝牙简介
- Android1.5中增加了蓝牙功用,立体声 Bluetooth 支撑:A2DP [Advanced Audio Distribution Profile]、AVCRP [Audio/Video Remote Control Profile],主动配对。
- Android2.0中支撑Bluetooth2.1协议。
- Android3.0中能让运用查询现已衔接上 Bluetooth 设备的 Bluetooth Profile、音频状况等,然后告诉用户。
- Android3.1中体系能够经过 Bluetooth HID 办法一起接入一到多款输入设备。
- Android4.0中新增支撑衔接 Bluetooth HDP [Health Device Profile)] 设备,经过第三方运用的支撑,用户能够衔接到医院、健身中心或许家庭等场合中的无线医疗设备和传感器。
- Android4.2中引进了一种新的针对 Android 设备优化的 Bluetooth 协议栈 BlueDroid,然后取代 BlueZ 协议栈。Bluedroid 协议栈由 Google 和 Broadcom 公司共同开发,相关于 BlueZ 协议栈,BlueDroid 进步了兼容性和可靠性。
- Android4.3中增加了对低功耗蓝牙的支撑,内置支撑 Bluetooth AVRCP 1.3,依据 Google 和 Broadcom 公司功用研制的针关于 Android 设备优化的新的蓝牙协议栈 BlueDroid。
- Android4.4中新增两种新 Proifle 支撑:HID [Human Interface Device]、MAP [Message Access Profile]
- Android5.0中支撑Bluetooth4.1协议。
- Android6.0中扫描蓝牙需求动态获取定位才行。
- Android7.0中支撑Bluetooth4.2协议。
- Android8.0中支撑Bluetooth5.0协议,强化了蓝牙音频的体现。比方编码/传输格局可选SBC、AAC、aptX/aptX HD、LDAC等四种,音质依次进步。
- Android10.0中支撑Bluetooth5.1协议,在5.0的根底上,增加了侧向功用和厘米级定位服务,大幅度进步了定位精度。使室内定位更精准。
- Android11.0中支撑Bluetooth5.2协议,增强版ATT协议,LE功耗操控和信号同步,衔接更快,更稳定,抗干扰性更好。
- Android12.0中支撑Bluetooth5.3协议,增强了经典蓝牙BR/EDR(根底速率和增强速率)的安全性。蓝牙5.3的推迟更低、抗干扰性更强、进步了电池续航时刻。体系引进了新的运转时权限 BLUETOOTH_SCAN、BLUETOOTH_ADVERTISE 和 BLUETOOTH_CONNECT权限,用于更好地管理运用于邻近蓝牙设备的衔接。
-
Android13.0中引进LE Audio支撑,LE Audio 是蓝牙技能联盟(SIG Q) 在 2020 年国际消费电子展上推出的新一代蓝牙低功耗音频技能,能以蓝牙低功耗状况下传递音频有利于进步蓝牙耳机续航力和性能,一起也导入新代代蓝牙音频编码 LC3(Low Complexity Communication Codec),以及多种音频分享、播送音频办法等,而且有利于助听器产品,能够到达低推迟、高音质等功用。
蓝牙低功耗音频LE Audio 算是蓝牙技能联盟开展 20 年全新的音频技能,中间历经8年开展与两次中心规格更新,也算是有史以来最大型开发项目,能够分许开发者经过23种不同文件装备与服务规范,开发出蓝牙高音质、新拓朴结构和省电音频设备。 - 目前最新的蓝牙协议是蓝牙5.3版别(截止到2023年9月22日)
- Android 4.3 开端,开端支撑BLE功用,但只支撑Central(中心人物or主机)
- Android 5.0开端,开端支撑Peripheral(外设人物or从机)
中心办法和外设办法是什么意思?
- Central Mode: Android端作为中心设备,衔接其他外围设备。
- Peripheral Mode:Android端作为外围设备,被其他中心设备衔接。在Android 5.0支撑外设办法之后,才算完结了两台Android手机经过BLE进行彼此通讯。
3. 蓝牙的播送和扫描
关于这部分内容,需求引进一个概念,GAP(Generic Access Profile),它用来操控设备衔接和播送。GAP 使你的设备被其他设备可见,并决议了你的设备是否能够或许怎样与设备进行交互。例如 Beacon 设备就仅仅向外发送播送,不支撑衔接;小米手环就能够与中心设备树立衔接。
在 GAP 中蓝牙设备能够向外播送数据包,播送包分为两部分: Advertising Data Payload(播送数据)和 Scan Response Data Payload(扫描回复),每种数据最长能够包括 31 byte。这儿播送数据是必需的,由于外设必需不断的向外播送,让中心设备知道它的存在。扫描回复是可选的,中心设备能够向外设恳求扫描回复,这儿包括一些设备额外的信息,例如设备的姓名。在 Android 中,体系会把这两个数据拼接在一起,回来一个 62 字节的数组。这些播送数据能够自己手动去解析,在 Android 5.0 也供给 ScanRecord 帮你解析,直接能够经过这个类取得有意义的数据。播送中能够有哪些数据类型呢?设备衔接特点,标识设备支撑的 BLE 办法,这个是有必要的。设备姓名,设备包括的要害 GATT service,或许 Service data,厂商自界说数据等等。
外围设备会设定一个播送距离,每个播送距离中,它会从头发送自己的播送数据。播送距离越长,越省电,一起也不太简略扫描到。
刚刚讲到,GAP决议了你的设备怎样与其他设备进行交互。答案是有2种办法:
彻底依据播送的办法
也有些状况是不需求衔接的,只要外设播送自己的数据即可。用这种办法首要目的是让外围设备,把自己的信息发送给多个中心设备。运用播送这种办法最典型的运用便是苹果的 iBeacon。这是苹果公司界说的依据 BLE 播送完结的功用,能够完结广告推送和室内定位。这也阐明晰,APP 运用 BLE,需求定位权限。
依据非衔接的,这种运用便是依赖 BLE 的播送,也叫作 Beacon。这儿有两个人物,发送播送的一方叫做 Broadcaster,监听播送的一方叫 Observer。
依据GATT衔接的办法
大部分状况下,外设经过播送自己来让中心设备发现自己,并树立 GATT 衔接,然后进行更多的数据交换。这儿有且仅有两个人物,主张衔接的一方,叫做中心设备—Central,被衔接的设备,叫做外设—Peripheral。
外围设备:这一般便是十分小或许简略的低功耗设备,用来供给数据,并衔接到一个更加相对强壮的中心设备,例如小米手环。
中心设备:中心设备相对比较强壮,用来衔接其他外围设备,例如手机等。
GATT 衔接需求特别留意的是:GATT 衔接是独占的。也便是一个 BLE 外设一起只能被一个中心设备衔接。一旦外设被衔接,它就会马上中止播送,这样它就对其他设备不行见了。当设备断开,它又开端播送。中心设备和外设需求双向通讯的话,仅有的办法便是树立 GATT 衔接。
GATT 通讯的两边是 C/S 联系。外设作为 GATT 服务端(Server),它维持了 ATT 的查找表以及 service 和 characteristic 的界说。中心设备是 GATT 客户端(Client),它向 Server 主张恳求。需求留意的是,一切的通讯事情,都是由客户端主张,而且接纳服务端的呼应。
4. BLE通讯根底
BLE通讯的根底有两个重要的概念,ATT和GATT。
ATT
全称 attribute protocol,中文名“特点协议”。它是 BLE 通讯的根底。
ATT 把数据封装,向外露出为“特点”,供给“特点”的为服务端,获取“特点”的为客户端。
ATT 是专门为低功耗蓝牙规划的,结构十分简略,数据长度很短。
GATT
全称 Generic Attribute Profile, 中文名“通用特点装备文件”。它是在ATT 的根底上,
对 ATT 进行的进一步逻辑封装,界说数据的交互办法和含义。GATT是咱们做 BLE 开发的时分直接触摸的概念。
GATT 层级
GATT依照层级界说了4个概念:装备文件(Profile)、服务(Service)、特征(Characteristic)和描述(Descriptor)。
他们的联系是这样的:Profile 便是界说了一个实践的运用场景,一个 Profile包括若干个 Service,
一个 Service 包括若干个 Characteristic,一个 Characteristic 能够包括若干 Descriptor。
-
Profile
Profile 并不是实践存在于 BLE 外设上的,它仅仅一个被 Bluetooth SIG 或许外设规划者预先界说的 Service 的调集。例如心率Profile(Heart Rate Profile)便是结合了 Heart Rate Service 和 Device Information Service。一切官方经过 GATT Profile 的列表能够从这儿找到。
-
Service
Service 是把数据分成一个个的独立逻辑项,它包括一个或许多个 Characteristic。每个 Service 有一个 UUID 仅有标识。 UUID 有 16 bit 的,或许 128 bit 的。16 bit 的 UUID 是官方经过认证的,需求花钱购买,128 bit 是自界说的,这个就能够自己随意设置。官方经过了一些规范 Service,完好列表在这儿。以 Heart Rate Service为例,能够看到它的官方经过 16 bit UUID 是 0x180D,包括 3 个 Characteristic:Heart Rate Measurement, Body Sensor Location 和 Heart Rate Control Point,而且界说了只要第一个是有必要的,它是可选完结的。
-
Characteristic
需求要点提一下Characteristic, 它界说了数值和操作,包括一个Characteristic声明、Characteristic特点、值、值的描述(Optional)。一般咱们讲的 BLE 通讯,其实便是对 Characteristic 的读写或许订阅告诉。比方在实践操作进程中,我对某一个Characteristic进行读,便是获取这个Characteristic的value。
-
UUID
Service、Characteristic 和 Descriptor 都是运用 UUID 仅有标示的。
UUID 是大局仅有标识,它是 128bit 的值,为了便于识别和阅览,一般以 “8位-4位-4位-4位-12位”的16进制标示,比方“12345678-abcd-1000-8000-123456000000”。
但是,128bit的UUID 太长,考虑到在低功耗蓝牙中,数据长度十分受限的状况,蓝牙又运用了所谓的 16 bit 或许 32 bit 的 UUID,办法如下:“0000XXXX-0000-1000-8000-00805F9B34FB”。除了 “XXXX” 那几位以外,其他都是固定,所以说,其实 16 bit UUID 是对应了一个 128 bit 的 UUID。这样一来,UUID 就大幅削减了,例如 16 bit UUID只要有限的 65536(16的四次方) 个。与此一起,由于数量有限,所以 16 bit UUID 并不能随意运用。蓝牙技能联盟现已预先界说了一些 UUID,咱们能够直接运用,比方“00001011-0000-1000-8000-00805F9B34FB”就一个是常见于BLE设备中的UUID。当然也能够花钱定制自界说的UUID。
二: BLE开发流程-扫描,衔接,发送和接纳数据,分包解包
demo地址:github.com/PangHaHa121…
demo演示1
涉及ble蓝牙通讯的客户端(中心设备)敞开、扫描、衔接、发送和接纳数据、分包解包,
和服务端(外围设备)初始化播送数据、开端播送、装备Services、Server回调操作
1,两台手机A 小米手机 为客户端,B 三星手机 为服务端
2,B敞开播送,A扫描设备,扫描到B Galaxy A20,树立衔接,
3,A经过仅有UUID服务 发送数据给B,B收到数据,显现日志
4,A设置回调告诉,B绑定告诉服务,回调给A
5,A写入协议给B,B经过回调告诉写入协议给A
demo演示2
两台手机都是主机,电脑为从机(电脑经过刺进HLK-B40蓝牙透传模块,取得蓝牙BLE衔接才能)
1,两台手机分别扫描低功耗蓝牙,并发现HLK-B40,
2,手机端与电脑树立衔接,并发送数据
3,电脑端运用蓝牙串口助手调试 收到数据
4,电脑端发送数据
5,手机端接纳数据
6,电脑端守时发送数据,手机端守时发送数据
下面详细讲解下客户端和服务端的开发进程流程
三: BLE客户端开发流程
1、请求权限
安卓手机涉及蓝牙权限问题,蓝牙开发需求在AndroidManifest.xml文件中增加权限声明:
<!--蓝牙权限-->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!--Android6及以上 动态请求方位权限-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!--Android12及以上 请求蓝牙扫描,衔接,播送权限-->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!--运用蓝牙低功耗BLE-->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
权限阐明:
在Android4.3 至 Android6.0 需求蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- true 表明手机有必要支撑BLE,否则无法装置!这儿设为false, 运转后在代码中查看-->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
在Android6.0(包括6.0) 到 Android12.0需求定位权限,包括模糊方位和精准方位,而且需求代码动态请求
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
Android11之前权限请求:
developer.android.com/guide/topic…
Android12权限请求:
developer.android.google.cn/about/versi…
在Android12.0及以上,需求请求蓝牙扫描,衔接,播送权限
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
如果App不需求获取方位权限能够增加运用不推导方位的flag,则不需求请求方位权限了
<!-- 设置最大支撑运用版别为30,即Android12和今后的高版别不再需求老权限了 -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!-- 仅当你能够强烈断言你的运用程序永远不会从蓝牙扫描成果中获取物理方位时,才包括“neverForLocation” -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
如果要在后台敞开服务扫描蓝牙,则还需求增加后台拜访方位权限
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
2、翻开蓝牙
在查找设备之前先要询问翻开手机蓝牙
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
//不支撑BLE
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
finish();
return;
}
// 敞开蓝牙
if(!mBluetoothAdapter.isEnabled()){
//不主张强制翻开蓝牙,官方主张经过Intent让用户挑选翻开蓝牙
//mBluetoothAdapter.enable();
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BLUETOOTH);
}
动态权限请求,请求权限后还需求查看手机GPS是否敞开,有没有定位权限和GPS是否翻开是两回事
List<String> mPermissionList = new ArrayList<>();
// Android 版别大于等于 12 时,请求新的蓝牙权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
mPermissionList.add(Manifest.permission.BLUETOOTH_SCAN);
mPermissionList.add(Manifest.permission.BLUETOOTH_ADVERTISE);
mPermissionList.add(Manifest.permission.BLUETOOTH_CONNECT);
//依据实践需求请求定位权限
mPermissionList.add(Manifest.permission.ACCESS_COARSE_LOCATION);
mPermissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
} else {
//Android 6.0开端 需求定位权限
mPermissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
mPermissionList.add(Manifest.permission.ACCESS_COARSE_LOCATION);
}
boolean hasPermission = false;
for (int i = 0; i < mPermissionList.size(); i++) {
int permissionCheck = ContextCompat.checkSelfPermission(this, mPermissionList.get(i));
hasPermission = permissionCheck == PackageManager.PERMISSION_GRANTED;
}
if (hasPermission) {
//现已有权限了
} else {
ActivityCompat.requestPermissions(this, mPermissionList.toArray(new String[0]), REQUEST_PERMISSION_CODE);
}
//敞开方位服务,支撑获取ble蓝牙扫描成果
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !isLocationOpen(getApplicationContext())) {
Intent enableLocate = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivityForResult(enableLocate, REQUEST_LOCATION_PERMISSION);
}
public static boolean isLocationOpen(final Context context){
LocationManager manager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
//gps定位
boolean isGpsProvider = manager.isProviderEnabled(LocationManager.GPS_PROVIDER);
//网络定位
boolean isNetWorkProvider = manager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
return isGpsProvider|| isNetWorkProvider;
}
3、查找设备
留意: BLE设备地址是动态改变(每隔一段时刻都会改变),而经典蓝牙设备是出厂就固定不变了!
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
// 下面运用Android5.0新增的扫描API,扫描回来的成果更友好,比方BLE播送数据曾经是byte[] scanRecord,
// 而新API帮咱们解析成ScanRecord类
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
// 扫描成果Callback
private ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
// result.getScanRecord() 获取BLE播送数据
BluetoothDevice device = result.getDevice() 获取BLE设备信息
String strName = bluetoothDevice.getName();
if (strName != null && strName.length() > 0) {
//增加设备到列表
}
}
};
bluetoothLeScanner.startScan(mScanCallback); //敞开扫描
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
bluetoothLeScanner.stopScan(mScanCallback); //中止扫描
isScanning = false;
}
}, 3000);
// 旧API是BluetoothAdapter.startLeScan(LeScanCallback callback)办法扫描BLE蓝牙设备,如下:
private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
//获取设备信息 device
String strName = device.getName();
if (strName != null && strName.length() > 0) {
//增加设备到列表
}
}
};
bluetoothAdapter.startLeScan(leScanCallback); //敞开扫描
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
bluetoothAdapter.stopLeScan(leScanCallback); //中止扫描
isScanning = false;
}
}, 3000);
4、衔接设备
经过扫描BLE设备,依据设备称号区分出方针设备targetDevice,下一步完结与方针设备的衔接,在衔接设备之前要中止查找蓝牙;中止查找一般需求一定的时刻来完结,最好调用中止查找函数之后加以100ms的延时,确保体系能够彻底中止查找蓝牙设备。中止查找之后发动衔接进程;
BLE蓝牙的衔接办法相对简略只需调用BluetoothDevice的connectGatt办法;
public BluetoothGatt connectGatt (Context context, boolean autoConnect, BluetoothGattCallback callback);
//示例
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mBluetoothGatt = bluetoothDevice.connectGatt(BleClientActivity.this, false, mBluetoothGattCallback, BluetoothDevice.TRANSPORT_LE);
} else {
mBluetoothGatt = bluetoothDevice.connectGatt(BleClientActivity.this, false, mBluetoothGattCallback);
}
参数阐明
- 回来值 BluetoothGatt: BLE蓝牙衔接管理类,首要担任与设备进行通讯;
- boolean autoConnect:主张置为false,能够进步衔接速度;
- BluetoothGattCallback callback 衔接回调,重要参数,BLE通讯的中心部分
5、设备通讯
与设备树立衔接之后与设备通讯,整个通讯进程都是在BluetoothGattCallback的异步回调函数中完结;
BluetoothGattCallback中首要回调函数如下:
private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) {
//衔接状况改变的Callback
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
//服务发现成功的Callback
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {
//写入Characteristic
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
//读取Characteristic
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic) {
//告诉Characteristic
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
//写入Descriptor
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
//读取Descriptor
}
};
上述几个回调函数是BLE开发中不行缺少的
6、等候设备衔接成功
当调用targetDevice.connectGatt(context, false, gattCallback)后体系会主动主张与BLE蓝牙设备的衔接,
若成功衔接到设备将回调onConnectionStateChange办法,其处理进程如下;
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) {
if (newState == BluetoothGatt.STATE_CONNECTED) {
Log.e(TAG, "设备衔接上 开端扫描服务");
// 衔接成功后,开端扫描服务
mBluetoothGatt.discoverServices();
}
if (newState == BluetoothGatt.STATE_DISCONNECTED) {
// 衔接断开
/*衔接断开后的相应处理*/
}
};
判别newState == BluetoothGatt.STATE_CONNECTED表明此时现已成功衔接到设备
7、敞开扫描服务
bluetoothGatt.discoverServices() 扫描BLE设备服务是安卓体系中关于BLE蓝牙开发的重要一步,一般在设备衔接成功后调用,
扫描到设备服务后回调onServicesDiscovered()函数,函数原型如下:
public BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt bluetoothGatt, int status, int newState) {
BluetoothDevice dev = bluetoothGatt.getDevice();
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
isConnected = true;
bluetoothGatt.discoverServices(); //发动服务发现
} else {
isConnected = false;
}
}
@Override
public void onServicesDiscovered(BluetoothGatt bluetoothGatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) { //BLE服务发现成功
private List<BluetoothGattService> servicesList;
//获取服务列表
servicesList = bluetoothGatt.getServices();
}
}
...
}
- BLE蓝牙协议下数据的通讯办法采用BluetoothGattService、BluetoothGattCharacteristic和BluetoothGattDescriptor三个首要的类完结通讯;
- BluetoothGattService 简称服务,是构成BLE设备协议栈的组成单位,一个蓝牙设备协议栈一般由一个或许多个BluetoothGattService组成;
- BluetoothGattCharacteristic 简称特征,一个服务包括一个或许多个特征,特征作为数据的基本单元;
- 一个BluetoothGattCharacteristic特征包括一个数据值和附加的关于特征的描述;
- BluetoothGattDescriptor:用于描述特征的类,其相同包括一个value值;
8、获取担任通讯的BluetoothGattCharacteristic
BLE蓝牙开发首要有担任通讯的BluetoothGattService完结的。当且称为通讯服务。通讯服务经过硬件工程师供给的UUID获取。获取办法如下:
- BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString(“蓝牙模块供给的担任通讯UUID字符串”));
- 通讯服务中包括担任读写的BluetoothGattCharacteristic,且分别称为notifyCharacteristic和writeCharacteristic。其间notifyCharacteristic担任敞开监听,也便是发动收数据的通道,writeCharacteristic担任写入数据;
具体操作办法如下:
BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString("蓝牙模块供给的担任通讯服务UUID字符串"));
// 例如办法如:49535343-fe7d-4ae5-8fa9-9fafd205e455
notifyCharacteristic = service.getCharacteristic(UUID.fromString("notify uuid"));
writeCharacteristic = service.getCharacteristic(UUID.fromString("write uuid"));
9、敞开监听
敞开监听,即树立与设备的通讯的首发数据通道,BLE开发中只要当客户端成功敞开监听后才能与服务端收发数据。敞开监听的办法如下:
mBluetoothGatt.setCharacteristicNotification(notifyCharacteristic, true)
BluetoothGattDescriptor descriptor = characteristic .getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
//若敞开监听成功则会回调BluetoothGattCallback中的onDescriptorWrite()办法,处理办法如下:
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//敞开监听成功,能够向设备写入指令了
Log.e(TAG, "敞开监听成功");
}
};
10、写入数据
BLE单次写的数据量巨细是有约束的,一般是20字节,能够测验经过requestMTU增大,但不确保能成功。分包写是一种解决方案,需求界说分包协议,假定每个包巨细20字节,分两种包,数据包和非数据包。关于数据包,头两个字节表明包的序号,剩余的都填充数据。关于非数据包,首要是发送一些操控信息。
监听成功后经过向 writeCharacteristic写入数据完结与服务端的通讯。写入办法如下:
//value为客户端向服务端发送的指令
writeCharacteristic.setValue(value);
mBluetoothGatt.writeCharacteristic(writeCharacteristic)
其间:value一般为Hex格局指令,其内容由设备通讯的蓝牙通讯协议规则;
11、接纳数据
若写入指令成功则回调BluetoothGattCallback中的onCharacteristicWrite()办法,阐明将数据现已发送给下位机;
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "发送成功");
}
}
若发送的数据符合通讯协议,则服务端会向客户端回复相应的数据。发送的数据经过回调onCharacteristicChanged()办法获取,其处理办法如下:
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
// value为设备发送的数据,依据数据协议进行解析
byte[] value = characteristic.getValue();
}
经过向服务端发送指令获取服务端的回复数据,即可完结与设备的通讯进程;
12、断开衔接
当与设备完结通讯之后之后一定要断开与设备的衔接,避免下次衔接设备出问题,调用以下办法断开与设备的衔接:
mBluetoothGatt.disconnect();
mBluetoothGatt.close();
四: BLE服务端开发流程
1、设置播送以及初始化播送数据
//播送设置(有必要)
AdvertiseSettings settings = new AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) //播送办法: 低功耗,平衡,低推迟
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) //发射功率等级: 极低,低,中,高
.setTimeout(0)
.setConnectable(true) //能否衔接,播送分为可衔接播送和不行衔接播送
.build();
//播送数据(有必要,播送发动就会发送)
AdvertiseData advertiseData = new AdvertiseData.Builder()
.setIncludeDeviceName(true) //包括蓝牙称号
.setIncludeTxPowerLevel(true) //包括发射功率等级
.addManufacturerData(1, new byte[]{23, 33}) //设备厂商数据,自界说
.build();
//扫描呼应数据(可选,当客户端扫描时才发送)
AdvertiseData scanResponse = new AdvertiseData.Builder()
.addManufacturerData(2, new byte[]{66, 66}) //设备厂商数据,自界说
.addServiceUuid(new ParcelUuid(UUID_SERVICE)) //服务UUID
// .addServiceData(new ParcelUuid(UUID_SERVICE), new byte[]{2}) //服务数据,自界说
.build();
2、开端播送
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
//BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// ============发动BLE蓝牙播送(广告) ===============
mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
mBluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback);
// BLE播送Callback
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
logTv("BLE播送敞开成功");
}
@Override
public void onStartFailure(int errorCode) {
logTv("BLE播送敞开失利,错误码:" + errorCode);
}
};
3、装备Services以及Characteristic
// 留意:有必要要敞开可衔接的BLE播送,其它设备才能发现并衔接BLE服务端!
// =============发动BLE蓝牙服务端======================================
BluetoothGattService service = new BluetoothGattService(UUID_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY);
//增加可读+告诉characteristic
BluetoothGattCharacteristic characteristicRead = new BluetoothGattCharacteristic(UUID_CHAR_READ_NOTIFY,BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);
characteristicRead.addDescriptor(new BluetoothGattDescriptor(UUID_DESC_NOTITY, BluetoothGattCharacteristic.PERMISSION_WRITE));
service.addCharacteristic(characteristicRead);
//增加可写characteristic
BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(UUID_CHAR_WRITE, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE);
service.addCharacteristic(characteristicWrite);
if (bluetoothManager != null){
mBluetoothGattServer = bluetoothManager.openGattServer(this, mBluetoothGattServerCallback);
}
mBluetoothGattServer.addService(service);
4、Server回调以及操作
/**
* 服务事情的回调
*/
private BluetoothGattServerCallback mBluetoothGattServerCallback = new BluetoothGattServerCallback() {
// 1.衔接状况发生改变时
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
String response = "CHAR_" + (int) (Math.random() * 100); //模仿数据
// 呼应客户端
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.getBytes());
}
// 3. onCharacteristicWriteRequest,接纳具体的字节
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) {
// 获取客户端发过来的数据
String requestStr = new String(requestBytes);
// 呼应客户端
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, requestBytes);
}
// 5.特征被读取。当回复呼应成功后,客户端会读取然后触发本办法
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
String response = "DESC_" + (int) (Math.random() * 100); //模仿数据
// 呼应客户端
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.getBytes());
}
// 2.描述被写入时,在这儿履行 bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS... 收,触发 onCharacteristicWriteRequest
@Override
public void onDescriptorWriteRequest(final BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
// 收到客户端发过来的数据
String valueStr = Arrays.toString(value);
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);// 呼应客户端
//4.处理呼应内容
// 简略模仿告诉客户端Characteristic改变
if (Arrays.toString(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE).equals(valueStr)) { //是否敞开告诉
deviceNotify = device;
descriptorNotify = descriptor;
final BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
new Thread(new Runnable() {
@Override
public void run() {
String response = "CHAR_" + (int) (Math.random() * 100); //模仿数据
characteristic.setValue(response);
//告诉客户端改变Characteristic
mBluetoothGattServer.notifyCharacteristicChanged(device, characteristic, false);
}
}).start();
}
}
@Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
}
@Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
}
};
5、Server主动写入数据告诉client
private void write(String response) {
if (deviceNotify == null && descriptorNotify == null) {
return;
}
if (TextUtils.isEmpty(response.trim())) {
return;
}
BluetoothGattCharacteristic characteristic = descriptorNotify.getCharacteristic();
characteristic.setValue(response);
mBluetoothGattServer.notifyCharacteristicChanged(deviceNotify, characteristic, false);
}
6、关闭播送,断开衔接
if (mBluetoothLeAdvertiser != null) {
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
}
if (mBluetoothGattServer != null) {
mBluetoothGattServer.close();
}
五:蓝牙操作的留意事项
1、怎么避免ble蓝牙衔接呈现133错误?
- Android 衔接外围设备的数量有限,当不需求衔接蓝牙设备的时分,有必要调用 BluetoothGatt#close 办法释放资源;
- 蓝牙 API 衔接蓝牙设备的超时时刻大概在 20s 左右,具体时刻看体系完结。有时分某些设备进行蓝牙衔接的时刻会很长,大概十多秒。如果自己手动设置了衔接超时时刻在某些设备上可能会导致接下来几次的衔接测验都会在 BluetoothGattCallback#onConnectionStateChange 回来 state == 133;
- 能否避免android设备与ble设备衔接/断开时上报的133这类错误?
- 1、在衔接失利或许断开衔接之后,调用 close 并改写缓存
- 2、尽量不要在startLeScan的时分测验衔接,先stopLeScan后再去连
- 3、对同一设备断开后再次衔接(衔接失利重连),哪怕调用完close,需求等候一段时刻(400毫秒试了1次,成果不 行;1000毫秒则再没呈现过问题)后再去connectGatt
- 4、能够在衔接前都startLeScan一下,成功率要高一点
2、单次写的数据巨细有20字节约束,怎么发送长数据?
BLE单次写的数据量巨细是有约束的,一般是20字节,能够测验经过requestMTU增大,但不确保能成功。分包写是一种解决方案,需求界说分包协议,假定每个包巨细20字节,分两种包,数据包和非数据包。关于数据包,头两个字节表明包的序号,剩余的都填充数据。关于非数据包,首要是发送一些操控信息。
总体流程如下:
- (1)、界说通讯协议,如下(这儿仅仅个举例,能够依据项目需求扩展)
音讯号(1个字节) | 功用(1个字节) | 子功用(1个字节) | 数据长度(2个字节) | 数据内容(N个字节) | CRC校验(1个字节) |
---|---|---|---|---|---|
01 | 01 | 01 | 0000 | – | 2D |
音讯号(1个字节) 功用(1个字节) 子功用(1个字节) 数据长度(2个字节) 数据内容(N个字节) CRC校验(1个字节)
01 01 01 0000 – 2D
- (2)、封装通用发送数据接口(拆包)
该接口依据会发送数据内容按最大字节数拆分(一般20字节)放入行列,拆分完后,依次从行列里取出发送 - (3)、封装通用接纳数据接口(组包)
该接口依据从接纳的数据按协议里的界说解析数据长度判读是否完好包,不是的话把每条音讯累加起来 - (4)、解析完好的数据包,进行事务逻辑处理
- (5)、协议还能够引进加密解密,需求留意的选算法参数的时分,加密后的长度最好跟原数据长度一致,这样不会影响拆包组包
3、在Android不同版别或不同的手机扫描不到蓝牙设备
一般都是Android版别适配以及不同ROM机型(小米/红米、华为/荣耀等)(EMUI、MIUI、ColorOS等)的权限问题
4、读写问题
蓝牙的写入操作, 读取操作有必要序列化进行. 写入数据和读取数据是不能一起进行的, 如果调用了写入数据的办法,
马上调用又调用写入数据或许读取数据的办法,第二次调用的办法会当即回来 false, 代表当前无法进行操作;
5、一个完好的数据包分析
AAAB5D65501E08040004001B130053D550F6
- AA – 前导帧(preamble)
- 0x50655DAB – 拜访地址(access address)
- 1E – LL帧头字段(LL header)
- 08 – 有用数据包长度(payload length)
- 04000400 – ATT数据长度,以及L2CAP通道编号
- 1B – notify command
- 0x0013 – 电量数据handle
- 0x53 – 真正要发送的电量数据
- 0xF650D5 – CRC24值
参阅文档:
蓝牙技能联盟:
www.bluetooth.com/zh-cn/learn…
BLE技能详解:
doc.iotxx.com/BLE%E6%8A%8…
Android蓝牙BLE开发指南:
developer.android.com/guide/topic…
BLE蓝牙开源库:
github.com/Jasonchenli…
github.com/dingjikerbo…
github.com/aicareles/A…
github.com/NordicSemic…
github.com/xiaoyaoyou1…