LE蓝牙接入记录(备忘)
什么是LE蓝牙?
在接入一个功能前肯定要对这个功能有多多少少的了解;
Q:什么是LE蓝牙?
A:LE蓝牙 = BLE(Bluetooh Low Energy) = 低功耗蓝牙,区别普通蓝牙的是它耗电极低。
Q:我接入它不能像接入普通蓝牙那样吗?
A:很抱歉并不能,Android官方中LE蓝牙的借口几乎完全不同于普通蓝牙的借口。
Q:那直接找Demo复制粘贴就好了,为啥要你要写这篇博客?
A:两个字 - “有坑”。
多说无益,先让我看看Demo吧
github - https://github.com/Like151320/BleBluetoothDemoApplication – 建议使用nordicsemi蓝牙包
搜索LE蓝牙设备
/**是否正在搜索中*/
private var isSearching = false
/**停止搜索计时器,若为null则意味着计时器不存在,及没在搜索中*/
private var stopScanTimer: Timer? = null
/**搜到的设备-不重复*/
private val searchedDeviceList = mutableListOf<BluetoothDevice>()
/**开始搜索。超时时间[outTime]、搜索结果不重复[searchCallback]*/
private fun searchBle(outTime: Long, searchCallback: (BluetoothDevice) -> Unit) {
if (isSearching) {
Toast.makeText(this, "已在搜索…", Toast.LENGTH_LONG).show()
return
}
isSearching = true
searchedDeviceList.clear()
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
//开始搜索LE蓝牙 - PS:注意Android官方警告 - 执行设备发现对于蓝牙适配器而言是一个非常繁重的操作过程,并且会消耗大量资源。 在找到要连接的设备后,确保始终使用 cancelDiscovery() 停止发现,然后再尝试连接。 此外,如果您已经保持与某台设备的连接,那么执行发现操作可能会大幅减少可用于该连接的带宽,因此不应该在处于连接状态时执行发现操作。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//AndroidAPI 21 更新的新搜索方式
val scanCallback = object : ScanCallback() {
/**搜索失败回调*/
override fun onScanFailed(errorCode: Int) {
isSearching = false
clearStopScanTimer()
Log.e(TAG, "onScanFailed: $errorCode")
}
/**搜到LE蓝牙后立即回调-[result]会重复*/
override fun onScanResult(callbackType: Int, result: ScanResult?) {
if (result?.device?.address != null) {
//过滤重复
val isContains = searchedDeviceList.any { device ->
device.address == result.device?.address
}
//不重复时保存
if (!isContains) {
searchedDeviceList.add(result.device)
searchCallback.invoke(result.device)
}
}
}
/**搜索结果列表回调,但[results]也有重复,在制定上报延时后使用,比[onScanResult]效果好*/
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
Log.e(
TAG, "onBatchScanResults: " +
"${results?.map {
"\n[${it.device?.address}]"
}}"
)
val noContainsDevice = mutableListOf<BluetoothDevice>()
results?.forEach { result ->
if (result.device?.address != null) {
//过滤重复
val isContains = searchedDeviceList.any { device ->
device.address == result.device?.address
}
//不重复时保存
if (!isContains) {
noContainsDevice.add(result.device)
}
}
}
if (noContainsDevice.isNotEmpty()) {
searchedDeviceList.addAll(noContainsDevice)
noContainsDevice.forEach { device ->
searchCallback.invoke(device)
}
}
}
}
val scanSetting = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)//扫描模式 - SCAN_MODE_LOW_LATENCY: 低延迟,高消耗,建议仅当前台搜索时使用此模式
.setReportDelay(500)//扫描报告延时,若设置此项,则搜到设备后不会立即通知,而是加入队列一起通知,回调会走 onBatchScanResults 而非 onScanResult
.build()
//扫描设置scanSetting决不能null PS:建议手动设置scanSetting,并添加ReportDelay
bluetoothAdapter.bluetoothLeScanner.startScan(null, scanSetting, scanCallback)
// bluetoothAdapter.bluetoothLeScanner.startScan(scanCallback)//使用默认scanSetting
if (stopScanTimer != null)//关闭上次计时
clearStopScanTimer()
//开启 停止搜索计时器
stopScanTimer = Timer()
stopScanTimer!!.schedule(object : TimerTask() {
override fun run() {
bluetoothAdapter.bluetoothLeScanner.stopScan(scanCallback)
isSearching = false
}
}, outTime)
} else {
//AndroidAPI 21 前的旧搜索方式
val scanCallback: (BluetoothDevice, Int, ByteArray) -> Unit =
{ device: BluetoothDevice, rssi: Int, scanRecord: ByteArray ->
if (device.address != null) {
//过滤重复
val isContains = searchedDeviceList.any { myDevice ->
myDevice.address == device.address
}
//不重复时保存
if (!isContains) {
searchedDeviceList.add(device)
searchCallback.invoke(device)
}
searchCallback.invoke(device)
}
}
bluetoothAdapter.startLeScan(scanCallback)
if (stopScanTimer != null)//关闭上次计时
clearStopScanTimer()
//开启 停止搜索计时器
stopScanTimer = Timer()
stopScanTimer!!.schedule(object : TimerTask() {
override fun run() {
bluetoothAdapter.stopLeScan(scanCallback)
isSearching = false
}
}, outTime)
}
}
/**清除停止搜索计时器*/
private fun clearStopScanTimer() {
stopScanTimer?.cancel()
stopScanTimer?.purge()
stopScanTimer = null
}
连接蓝牙设备
private var selectedAddress = ""
fun connection(address: String) {
if (!BluetoothAdapter.checkBluetoothAddress(address))
return
val device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address)
//连接蓝牙来获得蓝牙信息 PS:按照Android官方的说法,连接前会自动提示绑定 - 注:如果两台设备之前尚未配对,则在连接过程中,Android 框架会自动向用户显示配对请求通知或对话框(如图 3 所示)。因此,在尝试连接设备时,您的应用无需担心设备是否已配对。 您的 RFCOMM 连接尝试将被阻塞,直至用户成功完成配对或配对失败(包括用户拒绝配对、配对失败或超时)。
device.connectGatt(this, false, object : BluetoothGattCallback() {
/**连接状态改变,著名的 22、133 都出自这里
* 要注意 [status]和[newState]是两个数据,而不是一个数据的变化前与变化后的值*/
override fun onConnectionStateChange(
gatt: BluetoothGatt?, status: Int, newState: Int
) {
super.onConnectionStateChange(gatt, status, newState)
Log.d(TAG, "onConnectionStateChange: status = $status; newState = $newState")
if (status == BluetoothGatt.GATT_SUCCESS
&& newState == BluetoothProfile.STATE_CONNECTED
) {
//连接只是连接,不能通讯,发现服务后才算是准备好通讯了
Log.d(TAG, "连接成功")
gatt?.discoverServices()
}
}
/**发现了连接服务*/
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
super.onServicesDiscovered(gatt, status)
//这里才可以看到service
Log.d(TAG, "发现连接通道")
//保存特征码列表
val characteristicMap = mutableMapOf<String, String>()
for (service in gatt!!.services) {
//characteristic(特征码)列表
for (characteristic in service.characteristics) {
characteristicMap[characteristic.uuid.toString()] =
service.uuid.toString()
}
}
val deviceInfo = DeviceInfo(address, device.name, characteristicMap)
"连接了$address\n$deviceInfo".run {
Log.d(TAG, this)
runOnUiThread {
Toast.makeText(baseContext, this, Toast.LENGTH_LONG).show()
}
}
gatt.disconnect()//断开连接
}
})
}
但是当然,仅仅连接没什么卵用
与BL蓝牙设备通讯
/**开始连接*/
fun connection(address: String) {
if (!BluetoothAdapter.checkBluetoothAddress(address))
return
val device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address)
//连接蓝牙来获得蓝牙信息 PS:按照Android官方的说法,连接前会自动提示绑定 - 注:如果两台设备之前尚未配对,则在连接过程中,Android 框架会自动向用户显示配对请求通知或对话框(如图 3 所示)。因此,在尝试连接设备时,您的应用无需担心设备是否已配对。 您的 RFCOMM 连接尝试将被阻塞,直至用户成功完成配对或配对失败(包括用户拒绝配对、配对失败或超时)。
device.connectGatt(this, false, object : BluetoothGattCallback() {
/**连接状态改变,著名的 22、133 都出自这里
* 要注意 [status]和[newState]是两个数据,而不是一个数据的变化前与变化后的值*/
override fun onConnectionStateChange(
gatt: BluetoothGatt?, status: Int, newState: Int
) {
super.onConnectionStateChange(gatt, status, newState)
Log.d(TAG, "onConnectionStateChange: status = $status; newState = $newState")
if (status == BluetoothGatt.GATT_SUCCESS
&& newState == BluetoothProfile.STATE_CONNECTED
) {
//连接只是连接,不能通讯,发现服务后才算是准备好通讯了
Log.d(TAG, "连接成功")
gatt?.discoverServices()
}
}
/**发现了连接服务*/
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
super.onServicesDiscovered(gatt, status)
//这里才可以看到service
Log.d(TAG, "发现连接通道 status = $status")
if (status == BluetoothGatt.GATT_SUCCESS) {
//保存特征码列表
val characteristicMap = mutableMapOf<String, String>()
for (service in gatt!!.services) {
//characteristic(特征码)列表
for (characteristic in service.characteristics) {
characteristicMap[characteristic.uuid.toString()] =
service.uuid.toString()
}
}
//保存特征码通道ID
deviceInfo = MyBLEActivity.DeviceInfo(address, device.name, characteristicMap)
"连接了$address\n$deviceInfo".run {
Log.d(TAG, this)
runOnUiThread {
Toast.makeText(baseContext, this, Toast.LENGTH_LONG).show()
}
}
onServicesDiscovered(gatt, deviceInfo!!)
//监听通道发来的讯息
registerNotification(gatt, deviceInfo!!)
}
}
override fun onDescriptorWrite(
gatt: BluetoothGatt?, descriptor: BluetoothGattDescriptor?, status: Int
) {
super.onDescriptorWrite(gatt, descriptor, status)
if (status == BluetoothGatt.GATT_SUCCESS
&& descriptor?.value?.contentEquals(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) == true
)
//正在注册特征码
if (isRegisteringNotification)
registerNotification(gatt!!, deviceInfo!!, descriptor.uuid.toString())
}
var isRegisteringNotification = false
/**开始注册返回信息监听。 [deviceInfo]-所有需要写入注册的Descriptor;
* [registeredDescriptorID]-当前注册到哪个Descriptor了,会去注册下一个,null则从头开始注册。
* 配合 [isRegisteringNotification] 与 [onDescriptorWrite] 能注册全部的特征码。
* 为何不直接全部写入注册信息? 我也希望能一下子全部写入descriptor,但是有个大坑文档中都没有提到。
*
* 坑:底层与蓝牙的交互是一条通道,数据只能一条一条的处理,好比一个缆车连接两座山,当我 writeDescriptor 把数据丢进缆车后,
* 不等缆车运过去又 writeDescriptor 丢一条数据,但缆车中已经有数据了,所以我就会写入失败 writeDescriptor() 返回 false。
* 我必须得等缆车把数据运过去了,在等缆车运回来“成功”两个字,我才能写下一条数据,WTF。
*/
private fun registerNotification(
gatt: BluetoothGatt,
deviceInfo: MyBLEActivity.DeviceInfo,
registeredDescriptorID: String? = null
) {
isRegisteringNotification = true
//开启所有特征码的监听通知,开启一次即可,并且仅仅 setCharacteristicNotification 不能真的监听回调,还需要向设备写入监听注册
if (registeredDescriptorID == null) {
for (service in gatt.services) {
val characteristics = service.characteristics
for (characteristic in characteristics) {
val notificationResult =
gatt.setCharacteristicNotification(characteristic, true)//监听发来的通道数据
Log.d(TAG, "注册数据监听否? - ${characteristic.uuid} $notificationResult")
}
}
}
//寻找下个要写入注册的 descriptor
var nextDescriptor: BluetoothGattDescriptor? = null
var findDescriptor = registeredDescriptorID == null//从头开始 = 注册第一个 else 查找下一个
for (entry in deviceInfo.characteristicAndService) {
for (descriptor in gatt.getService(UUID.fromString(entry.value))
.getCharacteristic(UUID.fromString(entry.key))
.descriptors) {
//找到了已写入的 descriptor
if (descriptor.uuid.toString() == registeredDescriptorID) {
findDescriptor = true
continue
}
//这是下一个要写入的 descriptor
if (findDescriptor) {
nextDescriptor = descriptor
}
}
}
//去写入注册 descriptor
if (nextDescriptor != null) {
nextDescriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
val writeResult = gatt.writeDescriptor(nextDescriptor)
Log.d(TAG, "注册监听数据写入否? - ${nextDescriptor.uuid} $writeResult")
} else {
//已经全部写入注册
isRegisteringNotification = false
}
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?
) {
super.onCharacteristicChanged(gatt, characteristic)
val value = characteristic?.value
if (value != null) {
Log.e(TAG, Arrays.toString(value))
onReceiveData(gatt!!, characteristic, value)
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int
) {
super.onCharacteristicRead(gatt, characteristic, status)
}
})
}
/**连接成功了*/
@WorkerThread
private fun onServicesDiscovered(gatt: BluetoothGatt, deviceInfo: MyBLEActivity.DeviceInfo) {
mGatt = gatt
mCharacteristicAndService = deviceInfo.characteristicAndService
runOnUiThread {
listAdapter.clear()
listAdapter.addAll(deviceInfo.characteristicAndService.keys)
listAdapter.notifyDataSetChanged()
}
}
发送数据
/**实际发送数据操作*/
fun writeData(
gatt: BluetoothGatt, serviceID: String, characteristicID: String, data: ByteArray
) {
Log.d(TAG, "开始发送数据 ${Arrays.toString(data)} - s = $serviceID c = $characteristicID")
//获取发送通道
val service = gatt.getService(UUID.fromString(serviceID))
val characteristic = service.getCharacteristic(UUID.fromString(characteristicID))
//填入并发送数据
characteristic.value = data
val writeResult = gatt.writeCharacteristic(characteristic)
Log.d(TAG, "数据成功否? $writeResult")
logListAdapter.add(
"发送信息:" +
"cid:${characteristic.uuid}\n" +
"value:${Arrays.toString(data)} - $writeResult"
)
logListAdapter.notifyDataSetChanged()
Toast.makeText(this, if (writeResult) "发送成功" else "发送失败", Toast.LENGTH_LONG).show()
}
接收数据
前提:在连接中完成 - 1、调用API开启设备数据通知。2、向设备发送开启通知
/**接收数据*/
@WorkerThread
private fun onReceiveData(
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray
) {
runOnUiThread {
logListAdapter.add(
"收到信息:" +
"cid:${characteristic.uuid}\n" +
"value:${Arrays.toString(value)}"
)
logListAdapter.notifyDataSetChanged()
}
}
踩过的坑
接收不到设备发来的信息
- 确认开启了信息通知
val notificationResult = gatt.setCharacteristicNotification(characteristic, true)//监听发来的通道数据
Log.d(TAG, "注册数据监听否? - ${characteristic.uuid} $notificationResult")
- 确认写入了通知
nextDescriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
val writeResult = gatt.writeDescriptor(nextDescriptor)
Log.d(TAG, "注册监听数据写入否? - ${nextDescriptor.uuid} $writeResult")
- 若通知写入失败,可能是没有一个个写入
坑:底层与蓝牙的交互是一条通道,数据只能一条一条的处理,好比一个缆车连接两座山,当我 writeDescriptor 把数据丢进缆车后,不等缆车运过去又 writeDescriptor 丢一条数据,但缆车中已经有数据了,所以我就会写入失败 writeDescriptor() 返回 false。我必须得等缆车把数据运过去了,在等缆车运回来“成功”两个字,我才能写下一条数据,WTF。
status = 22
这个错误码,搜索结果少的可怜:
表现效果为:
1、直接连接手环->连接成功->系统弹出配对提示->点击不配对-> status = 22
2、直接连接手环->连接成功->系统弹出配对提示->点击配对->从此正常。但是当第二次打开应用时,永远 status = 22
https://blog.csdn.net/shangming150/article/details/82251860 - 有说要重复连接的,但我这里不行
最终解决发现:是设备的问题,设备人员修复了绑定协议后,先解绑再绑定成功就不会再提示22了
设备人员说是要:手动解绑来清除绑定记录,之后绑定来创建绑定记录,绑定记录相同才能连接(感觉有毒)
22 = 设备端断开了连接
status = 133
1、我这里这个错误会固定在蓝牙同时连接的设备超过6个时出现,所以我限制了它最多不能同时连接6个设备
2、当不扫描就直接连接某蓝牙时,可能会出现133 (未验证)
绑定失败
同时绑定多个蓝牙时(无论是createBond还是自动弹出绑定提示)只有第一个调用蓝牙能绑定成功。
之后的蓝牙虽然发起了绑定,却不会受到任何绑定状态广播。
所以要绑定多个设备的蓝牙时需要一个一个去绑定。解除绑定是否有相同的坑没有测试
扫描不到设备
1、若设备刚刚被绑定,是会扫描不到的,除非重开蓝牙或解绑(我的小米6证实)
2、若长期的持续的进行扫描,有可能导致扫描失效,需要注意扫描频率(未验证)
其它错误码
这些错误码取自
//蓝牙连接包 —— https://github.com/NordicSemiconductor/Android-BLE-Library
implementation ‘no.nordicsemi.android:ble:2.0.3’
的类:GattError
/**
* Parses the GATT and HCI errors to human readable strings.
* <p>
* See: <a href="https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/master/stack/include/gatt_api.h">gatt_api.h</a> for details.<br>
* See also: <a href="https://android.googlesource.com/platform/external/libnfc-nci/+/master/src/include/hcidefs.h#447">hcidefs.h</a> for other possible HCI errors.
*/
public class GattError {
public static final int GATT_SUCCESS = BluetoothGatt.GATT_SUCCESS;
public static final int GATT_CONN_L2C_FAILURE = 0x01;
public static final int GATT_CONN_TIMEOUT = 0x08;
public static final int GATT_CONN_TERMINATE_PEER_USER = 0x13;
public static final int GATT_CONN_TERMINATE_LOCAL_HOST = 0x16;
public static final int GATT_CONN_FAIL_ESTABLISH = 0x3E;
public static final int GATT_CONN_LMP_TIMEOUT = 0x22;
public static final int GATT_CONN_CANCEL = 0x0100;
public static final int GATT_ERROR = 0x0085; // Device not reachable
public static final int GATT_INVALID_HANDLE = 0x0001;
public static final int GATT_READ_NOT_PERMIT = 0x0002;
public static final int GATT_WRITE_NOT_PERMIT = 0x0003;
public static final int GATT_INVALID_PDU = 0x0004;
public static final int GATT_INSUF_AUTHENTICATION = 0x0005;
public static final int GATT_REQ_NOT_SUPPORTED = 0x0006;
public static final int GATT_INVALID_OFFSET = 0x0007;
public static final int GATT_INSUF_AUTHORIZATION = 0x0008;
public static final int GATT_PREPARE_Q_FULL = 0x0009;
public static final int GATT_NOT_FOUND = 0x000a;
public static final int GATT_NOT_LONG = 0x000b;
public static final int GATT_INSUF_KEY_SIZE = 0x000c;
public static final int GATT_INVALID_ATTR_LEN = 0x000d;
public static final int GATT_ERR_UNLIKELY = 0x000e;
public static final int GATT_INSUF_ENCRYPTION = 0x000f;
public static final int GATT_UNSUPPORT_GRP_TYPE = 0x0010;
public static final int GATT_INSUF_RESOURCE = 0x0011;
public static final int GATT_CONTROLLER_BUSY = 0x003A;
public static final int GATT_UNACCEPT_CONN_INTERVAL = 0x003B;
public static final int GATT_ILLEGAL_PARAMETER = 0x0087;
public static final int GATT_NO_RESOURCES = 0x0080;
public static final int GATT_INTERNAL_ERROR = 0x0081;
public static final int GATT_WRONG_STATE = 0x0082;
public static final int GATT_DB_FULL = 0x0083;
public static final int GATT_BUSY = 0x0084;
public static final int GATT_CMD_STARTED = 0x0086;
public static final int GATT_PENDING = 0x0088;
public static final int GATT_AUTH_FAIL = 0x0089;
public static final int GATT_MORE = 0x008a;
public static final int GATT_INVALID_CFG = 0x008b;
public static final int GATT_SERVICE_STARTED = 0x008c;
public static final int GATT_ENCRYPTED_NO_MITM = 0x008d;
public static final int GATT_NOT_ENCRYPTED = 0x008e;
public static final int GATT_CONGESTED = 0x008f;
public static final int GATT_CCCD_CFG_ERROR = 0x00FD;
public static final int GATT_PROCEDURE_IN_PROGRESS = 0x00FE;
public static final int GATT_VALUE_OUT_OF_RANGE = 0x00FF;
public static final int TOO_MANY_OPEN_CONNECTIONS = 0x0101;
/**
* Converts the connection status given by the
* {@link android.bluetooth.BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)}
* to error name.
*
* @param error the status number.
* @return The error name as stated in the links in {@link GattError} documentation.
*/
public static String parseConnectionError(final int error) {
switch (error) {
case GATT_SUCCESS:
return "SUCCESS";
case GATT_CONN_L2C_FAILURE:
return "GATT CONN L2C FAILURE";
case GATT_CONN_TIMEOUT:
return "GATT CONN TIMEOUT";
case GATT_CONN_TERMINATE_PEER_USER:
return "GATT CONN TERMINATE PEER USER";
case GATT_CONN_TERMINATE_LOCAL_HOST:
return "GATT CONN TERMINATE LOCAL HOST";
case GATT_CONN_FAIL_ESTABLISH:
return "GATT CONN FAIL ESTABLISH";
case GATT_CONN_LMP_TIMEOUT:
return "GATT CONN LMP TIMEOUT";
case GATT_CONN_CANCEL:
return "GATT CONN CANCEL ";
case GATT_ERROR:
return "GATT ERROR"; // Device not reachable
default:
return "UNKNOWN (" + error + ")";
}
}
/**
* Converts the Bluetooth communication status given by other BluetoothGattCallbacks to error
* name. It also parses the DFU errors.
*
* @param error the status number.
* @return The error name as stated in the links in {@link GattError} documentation.
*/
public static String parse(final int error) {
switch (error) {
case GATT_INVALID_HANDLE:
return "GATT INVALID HANDLE";
case GATT_READ_NOT_PERMIT:
return "GATT READ NOT PERMIT";
case GATT_WRITE_NOT_PERMIT:
return "GATT WRITE NOT PERMIT";
case GATT_INVALID_PDU:
return "GATT INVALID PDU";
case GATT_INSUF_AUTHENTICATION:
return "GATT INSUF AUTHENTICATION";
case GATT_REQ_NOT_SUPPORTED:
return "GATT REQ NOT SUPPORTED";
case GATT_INVALID_OFFSET:
return "GATT INVALID OFFSET";
case GATT_INSUF_AUTHORIZATION:
return "GATT INSUF AUTHORIZATION";
case GATT_PREPARE_Q_FULL:
return "GATT PREPARE Q FULL";
case GATT_NOT_FOUND:
return "GATT NOT FOUND";
case GATT_NOT_LONG:
return "GATT NOT LONG";
case GATT_INSUF_KEY_SIZE:
return "GATT INSUF KEY SIZE";
case GATT_INVALID_ATTR_LEN:
return "GATT INVALID ATTR LEN";
case GATT_ERR_UNLIKELY:
return "GATT ERR UNLIKELY";
case GATT_INSUF_ENCRYPTION:
return "GATT INSUF ENCRYPTION";
case GATT_UNSUPPORT_GRP_TYPE:
return "GATT UNSUPPORT GRP TYPE";
case GATT_INSUF_RESOURCE:
return "GATT INSUF RESOURCE";
case GATT_CONN_LMP_TIMEOUT:
return "GATT CONN LMP TIMEOUT";
case GATT_CONTROLLER_BUSY:
return "GATT CONTROLLER BUSY";
case GATT_UNACCEPT_CONN_INTERVAL:
return "GATT UNACCEPT CONN INTERVAL";
case GATT_ILLEGAL_PARAMETER:
return "GATT ILLEGAL PARAMETER";
case GATT_NO_RESOURCES:
return "GATT NO RESOURCES";
case GATT_INTERNAL_ERROR:
return "GATT INTERNAL ERROR";
case GATT_WRONG_STATE:
return "GATT WRONG STATE";
case GATT_DB_FULL:
return "GATT DB FULL";
case GATT_BUSY:
return "GATT BUSY";
case GATT_ERROR:
return "GATT ERROR";
case GATT_CMD_STARTED:
return "GATT CMD STARTED";
case GATT_PENDING:
return "GATT PENDING";
case GATT_AUTH_FAIL:
return "GATT AUTH FAIL";
case GATT_MORE:
return "GATT MORE";
case GATT_INVALID_CFG:
return "GATT INVALID CFG";
case GATT_SERVICE_STARTED:
return "GATT SERVICE STARTED";
case GATT_ENCRYPTED_NO_MITM:
return "GATT ENCRYPTED NO MITM";
case GATT_NOT_ENCRYPTED:
return "GATT NOT ENCRYPTED";
case GATT_CONGESTED:
return "GATT CONGESTED";
case GATT_CCCD_CFG_ERROR:
return "GATT CCCD CFG ERROR";
case GATT_PROCEDURE_IN_PROGRESS:
return "GATT PROCEDURE IN PROGRESS";
case GATT_VALUE_OUT_OF_RANGE:
return "GATT VALUE OUT OF RANGE";
case TOO_MANY_OPEN_CONNECTIONS:
return "TOO MANY OPEN CONNECTIONS";
default:
return "UNKNOWN (" + error + ")";
}
}
}