1 传统蓝牙与低功耗蓝牙
传统蓝牙也叫经典蓝牙,经典蓝牙模块泛指支持蓝牙协议4.0以下的模块,有v1.1/1.2/2.0/2.1/3.0。经典蓝牙支持音频(HFP/HSP, A2DP)和数据(SPP, HID, OPP, PBAP等)两大类协议,通常用于数据量较大的传输,比如蓝牙耳机传递音乐,比如汽车的蓝牙免提通讯以及车载蓝牙娱乐系统,经典蓝牙由于功耗较大,逐渐在移动互联网中淘汰。
低功耗蓝牙模块指支持蓝牙协议4.0及以上的模块,Bluetooth Low Energy,简称BLE。低功耗蓝牙功耗低,非常省电,但低功耗蓝牙不支持音频协议,适用于传输数据量较少的应用,比如智能家居,血压计,温度传感器等。
低功耗蓝牙也分为单模芯片和双模芯片两种类型,双模芯片中和了两种蓝牙模块的优缺点,低功耗的同时还能传输音频。
2 申请权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- 搜索蓝牙需要,因为蓝牙可以被用来定位,所以需要定位权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
3 蓝牙基本操作
3.1 获取蓝牙适配器
// 蓝牙适配器
private val mBluetoothAdapter: BluetoothAdapter by lazy {
// 初始化蓝牙适配器
val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothManager.adapter
}
3.2 判断是否开启蓝牙
// 是否开启蓝牙
fun isBluetoothEnabled():Boolean{
return mBluetoothAdapter.isEnabled
}
3.3 开启蓝牙
// 开启蓝牙
fun openBluetooth():BluetoothHelper{
if (!mBluetoothAdapter.isEnabled) {
if(!mBluetoothAdapter.enable()){// 尝试开启蓝牙,返回值为是否成功开启蓝牙
showToast("建议允许开启蓝牙操作")
}
}
return this
}
3.4 检测蓝牙状态变化的广播接收器
// 蓝牙状态改变广播接收器
private val mBluetoothStateReceiver by lazy{
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)) {
BluetoothAdapter.STATE_ON -> {showToast("蓝牙已打开")}
BluetoothAdapter.STATE_OFF -> {showToast("蓝牙已关闭")}
BluetoothAdapter.STATE_TURNING_ON -> {showToast("蓝牙正在打开")}
BluetoothAdapter.STATE_TURNING_OFF -> {showToast("蓝牙正在关闭")}
}
}
}
}
3.5 扫描周围蓝牙设备
// 搜索蓝牙设备
fun searchDevices():BluetoothHelper{
if (mBluetoothAdapter.isDiscovering){// 如果正在搜索则取消搜索
mBluetoothAdapter.cancelDiscovery()
}else{
mBluetoothAdapter.startDiscovery()
}
return this
}
3.6 停止扫描蓝牙设备
// 停止扫描蓝牙设备
fun stopSearchDevices():BluetoothHelper{
if (mBluetoothAdapter.isDiscovering){
mBluetoothAdapter.cancelDiscovery()
}
return this
}
3.7 扫描到蓝牙设备的广播接收器
// 蓝牙扫描广播接收器
private val mBluetoothScanReceiver by lazy{
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
val action = intent.action
if (BluetoothDevice.ACTION_FOUND == action) {// 扫描到蓝牙设备
// 获取扫描到的设备
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
// 设备相关信息
// device.name?:设备名
// device.address:设备MAC地址
// device.bluetoothClass.majorDeviceClass:设备类型
}else if (BluetoothDevice.ACTION_ACL_CONNECTED == action) {// 扫描到已经被连接的蓝牙设备(附近已经建立连接的蓝牙设备,该连接不一定是和本中央设备)
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
}
}
}
}
4 蓝牙通讯
4.1 UUID
UUID(Universally Unique Identifier)是一个十六进制数字,是一种唯一标识码,也是一种软件构建标准。一个UUID有128bit(位),即16个字节。由于128位太长了使用不方便,因此通常会预设共通的部分,蓝牙联盟统一预设的UUID是00000000-0000-1000-8000-00805F9B34FB,我们称它为蓝牙Base UUID。有了蓝牙Base UUID就可以使用16bit或者32bit来表示实际长度为128bit的UUID了,具体的转换方式是,将16bit或32bit的UUID左移96bit后加上Base UUID就能得到实际的UUID。
4.2 服务、特征、属性与描述符等概念
简单描述服务、特征和属性间的关系,假设把一个蓝牙设备比作一个公司,公司有若干个部门,每一个部门就是一个服务。每个部门有若干个组员,每个组员就是一个特征,每个组员会不同的技能,每个技能就是一个属性。当我们需要完成一项任务时,就需要将任务交个对应部门的对应员工,也就是说找到蓝牙设备的某个服务里的某个特征。公司为了标识每个部门和部员,因此给定了部门编号和员工编号,也就是服务UUID和特征UUID。
Descriptor是描述符,被用来定义特征的属性,描述符比起属性则更为详尽。
中央设备,指的是客户端也叫主机或中心设备,通常指代手机。中央设备可以扫描并连接多个外围设备,外围设备即蓝牙设备。
4.3 基于GATT发现服务获取UUID
低功耗蓝牙设备使用的是GATT(Generic Attribute Profile)蓝牙协议,该协议定义了基于Service和Characteristic的蓝牙通信方式。需要注意的是GATT在连接时是独占的,一旦两个设备基于GATT连接后,它们就会停止对外发送蓝牙广播,因此其他蓝牙设备是无法搜索到已经基于GATT连接的蓝牙设备了。
4.3.1 GATT连接(即连接蓝牙设备)
// 蓝牙gatt
var mBluetoothGatt: BluetoothGatt? = null
// gatt连接蓝牙设备
fun connect(device: BluetoothDevice, context: Context) {
mBluetoothGatt = device.connectGatt(context,false, mGattCallback)
}
4.3.2 GATT连接时传入的回调
// gatt连接回调
private val mGattCallback by lazy{
object: BluetoothGattCallback() {
// 连接状态改变时回调
override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
super.onConnectionStateChange(gatt, status, newState)
if (newState == BluetoothProfile.STATE_CONNECTED) {// 成功连接
mBluetoothGatt?.discoverServices() // 调用发现服务(即获取到蓝牙设备所提供的所有服务)
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {// 断开连接
}
}
// 调用发现服务时回调
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
super.onServicesDiscovered(gatt, status)
if (status == BluetoothGatt.GATT_SUCCESS) {// 当前仍处于连接状态的话
val bluetoothGattServices = gatt!!.services // 获取蓝牙设备提供的所有服务
// 遍历所有服务
bluetoothGattServices.forEachIndexed { index, bluetoothGattService ->
val uuid = bluetoothGattService.uuid // 获取到此服务的UUID
val bluetoothGattCharacteristics = bluetoothGattService.characteristics // 获取到此服务的所有特征
Log.d(TAG, "服务${index+1}的UUID为:$uuid")
Log.d(TAG, "服务${index+1}有以下特征:")
// 遍历该服务的所有特征
bluetoothGattCharacteristics.forEachIndexed { index, bluetoothGattCharacteristic ->
Log.d(TAG, " 特征${index+1}的UUID:${bluetoothGattCharacteristic.uuid}") // 输出当前特征的UUID
val charaProp = bluetoothGattCharacteristic.properties// 获取当前特征的所有属性
// bluetoothGattCharacteristic.descriptors // 获取当前特征的所有描述符
if (charaProp and BluetoothGattCharacteristic.PROPERTY_READ > 0) {
Log.d(TAG, " 特征${index+1}有read属性")
}
if (charaProp and BluetoothGattCharacteristic.PROPERTY_WRITE > 0) {
Log.d(TAG, " 特征${index+1}有write属性")
}
if (charaProp and BluetoothGattCharacteristic.PROPERTY_NOTIFY > 0) {
Log.d(TAG, " 特征${index+1}有notify属性")
}
if (charaProp and BluetoothGattCharacteristic.PROPERTY_INDICATE > 0) {
Log.d(TAG, " 特征${index+1}有indicate属性")
}
}
}
}
}
// 读数据时回调
override fun onCharacteristicRead(
gatt: BluetoothGatt?,
characteristic: BluetoothGattCharacteristic?,
status: Int
) {}
// 写数据时回调
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
super.onCharacteristicWrite(gatt, characteristic, status)
if (status == BluetoothGatt.GATT_SUCCESS) {
// 发送成功
} else {
// 发送失败
}
}
// 远程数据改变回调
override fun onCharacteristicChanged(
gatt: BluetoothGatt?,
characteristic: BluetoothGattCharacteristic?
) {
super.onCharacteristicChanged(gatt, characteristic)
characteristic?.apply {// 发生数据变化的特征
Log.d(TAG, "onCharacteristicChanged: ${characteristic.uuid}")
}
}
}
}
一个特征拥有一至多个属性,公有四个属性。
属性名 | 说明 |
---|---|
read | 该特征具有读取数据的能力,可以通过该特征从蓝牙设备获取数据 |
write | 该特征具有写入数据的能力,可以通过该特征向蓝牙设备写入数据 |
notify | 蓝牙设备可以通过此特征主动向我们发送数据 |
indicate | 蓝牙设备可以通过此特征向我们建立一个可靠连接,意思就是向我们发送数据后我们会应答确认一次,以此保证连接的可靠性 |
4.3.3 向蓝牙设备发送数据
我们可以通过具有read属性的特征向蓝牙设备发送数据,每个特征对应的功能是不同的,因此要知道蓝牙设备提供的每个特征对应什么功能。可以通过UUID的前缀简单判断部分特征的功能。
查询链接:
https://blog.youkuaiyun.com/qq_43680229/article/details/84106398
https://www.bluetooth.com/specifications/assigned-numbers/
找到指定特征后,通过此特征向蓝牙设备发送数据
//蓝牙Gatt连接服务
private var mBluetoothGatt: BluetoothGatt? = null
//某个特征
private var mBluetoothGattCharacteristic:BluetoothGattCharacteristic? = null
//发送数据
fun sendMess(mess:String){
mBluetoothGattCharacteristic?.setValue(mess)// 修改此特征的值
mBluetoothGatt?.writeCharacteristic(mBluetoothGattCharacteristic) // 写入此特征来向蓝牙设备发送数据
}