Android 從 4.3(API Level 18) 開(kāi)始支持低功耗藍(lán)牙,但是只支持作為中心設(shè)備(Central)模式,這就意味著 Android 設(shè)備只能主動(dòng)掃描和鏈接其他外圍設(shè)備(Peripheral)。從 Android 5.0(API Level 21) 開(kāi)始兩種模式都支持。
低功耗藍(lán)牙開(kāi)發(fā)算是較偏技術(shù),實(shí)際開(kāi)發(fā)中坑是比較多的,網(wǎng)上有很多文章介紹使用和經(jīng)驗(yàn)總結(jié),但是有些問(wèn)題答案不好找,甚至有些誤導(dǎo)人,比如 :獲取已經(jīng)連接的藍(lán)牙,有的是通過(guò)反射,一大堆判斷,然而并不是對(duì)所有手機(jī)有用,關(guān)于Ble傳輸速率問(wèn)題的解決,都是默認(rèn)Android每次只能發(fā)送20個(gè)字節(jié),然而也并不是,,,下面進(jìn)入正文。
Ble的使用 官網(wǎng)
1. 權(quán)限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!--Android 6.0及后續(xù)版本掃描藍(lán)牙,需要定位權(quán)限(進(jìn)入GPS設(shè)置,可以看到藍(lán)牙定位)-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />//9.0以上
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />
<!--不支持低功耗的設(shè)備也可以安裝,否則可以設(shè)置 required ="true"-->
2. 初始 使用 (在掃描前要記得先打開(kāi)藍(lán)牙成功,否則會(huì)很長(zhǎng)時(shí)間不響應(yīng))
fun initBle() {
// 檢查藍(lán)牙開(kāi)關(guān)
val adapter = BluetoothAdapter.getDefaultAdapter()
if (adapter == null) {
ToastUtils.showShortSafe("本機(jī)沒(méi)有找到藍(lán)牙硬件或驅(qū)動(dòng)!")
return
} else {
/*if (!adapter.isEnabled()) {
//直接開(kāi)啟藍(lán)牙
adapter.enable();
//跳轉(zhuǎn)到設(shè)置界面
//startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 112);
}*/
if (!adapter.isEnabled) {
val enableIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
mContext.startActivityForResult(enableIntent, REQUEST_ENABLE_BT)
// 在onActivityResult里提示是否開(kāi)成功根據(jù)Activity.RESULT_OK
}
}
// 檢查是否支持BLE藍(lán)牙
if (!mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
ToastUtils.showShortSafe("本機(jī)不支持低功耗藍(lán)牙!")
return
}
// Android 6.0動(dòng)態(tài)請(qǐng)求位置權(quán)限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val permissions = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_COARSE_LOCATION)
for (str in permissions) {
if (mContext.checkSelfPermission(str) != PackageManager.PERMISSION_GRANTED) {
mContext.requestPermissions(permissions, REQUEST_ENABLE_LC)
break
}
}
}
}
3. 掃描
這里用的是 Android5.0 新增的掃描API,
- 盡量不要在掃描結(jié)果方法做耗時(shí)操作,它會(huì)快速連續(xù)調(diào)用(二期同一個(gè)設(shè)備連續(xù)返回)
- 添加用服務(wù)uuid做過(guò)濾,會(huì)加快掃描速度節(jié)省時(shí)間,比自己通過(guò)代碼刷選好很多
- 掃描是耗電較大的操作,注意定時(shí)停止,官方 Demo 寫的10m
// 掃描BLE藍(lán)牙(不會(huì)掃描經(jīng)典藍(lán)牙)
private void scanBle() {
getConnedDevs();
isScanning = true;
// BluetoothAdapter bluetoothAdapter = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE).getDefaultAdapter();
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
final BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
// Android5.0新增的掃描API,掃描返回的結(jié)果更友好,比如BLE廣播數(shù)據(jù)以前是byte[] scanRecord,而新API幫我們解析成ScanRecord類
bluetoothLeScanner.startScan(buildScanFilters(), buildScanSettings(), mScanCallback);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
bluetoothLeScanner.stopScan(mScanCallback); //停止掃描
isScanning = false;
}
}, 3000);//(耗電)3秒后停止,根據(jù)需要設(shè)置
}
/**
* 過(guò)濾
*
* @return
*/
private List<ScanFilter> buildScanFilters() {
List<ScanFilter> scanFilters = new ArrayList<>();
ScanFilter.Builder builder = new ScanFilter.Builder();
// 通過(guò)服務(wù) uuid 過(guò)濾自己要連接的設(shè)備
builder.setServiceUuid(new ParcelUuid(BleUtils.Companion.getUUID_SERVICE()));
scanFilters.add(builder.build());
return scanFilters;
}
private ScanSettings buildScanSettings() {
ScanSettings.Builder builder = new ScanSettings.Builder();
builder.setScanMode(ScanSettings.SCAN_MODE_LOW_POWER);//低耗電模式
//SCAN_MODE_LOW_LATENCY 低延遲 我用的(耗電,無(wú)所謂,又不是一直掃描的,3秒就停止了)
return builder.build();
}
private final ScanCallback mScanCallback = new ScanCallback() {// 掃描Callback
@Override
public void onScanResult(int callbackType, ScanResult result) {//盡量不做耗時(shí)操作,此方法會(huì)快速重復(fù)多次調(diào)用,返回相同的設(shè)備
String address = result.getDevice().getAddress();
if (!mDevices.contains(dev)) {
mDevices.add(dev);
notifyDataSetChanged();
Log.i(TAG, "onScanResult: " + result); // result.getScanRecord() 獲取BLE廣播數(shù)據(jù)
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
Log.i(TAG, "onScanResult: " + " " + results);
}
};
// 重新掃描
public void reScan() {
mDevices.clear();
notifyDataSetChanged();
scanBle();
}
這里說(shuō)一下,如果做藍(lán)牙設(shè)備管理頁(yè)面,可能區(qū)分是否是已連接的設(shè)備,網(wǎng)上又通過(guò)反射或其他挺麻煩的操作,也不見(jiàn)得獲取到,官方Api 就有提供
BluetoothManager bluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
List<BluetoothDevice> connectedDevices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT);
4. 連接
- 在設(shè)備列表 點(diǎn)擊 dev.connectGatt(mContext, false, mBluetoothGattCallback) -> 這里的false 是不自動(dòng)連,如果是true 使用時(shí)連接很慢。
- onConnectionStateChange()(1 成功 繼續(xù)啟動(dòng)發(fā)現(xiàn)服務(wù) 2 . 經(jīng)常斷鏈有時(shí)候連接一次不成功,就重復(fù) dev.connectGatt()->
- onServicesDiscovered() 服務(wù)發(fā)現(xiàn)成功好會(huì)把設(shè)備所有 Service 和 Service 里的 characteristics 及 characteristic.descriptors 全返回來(lái)(可以用來(lái)展示,調(diào)試和判斷自己要用的服務(wù) 特征 到底有沒(méi)有成功發(fā)現(xiàn))然后setNotify() 需要接收數(shù)據(jù)時(shí)得打開(kāi)通知 ->
- onDescriptorWrite() 寫入通知成功后mBluetoothGatt!!.requestMtu() 請(qǐng)求該設(shè)備的Mtu ,默認(rèn)23個(gè)字節(jié) 我們用的設(shè)備我申請(qǐng)256個(gè)字節(jié)最后只能拿到244個(gè)字節(jié)->
- onMtuChanged 拿到mtu-3 為什么減3 后面做數(shù)據(jù)分包->
-
最后通知連接成功 mBleConnCallBack!!.onConntionedCallBack(status, isConnected)
這個(gè)callback是我自己寫的回調(diào) 才能做后面的發(fā)送,接收數(shù)據(jù)
image.png
/**
* 連接設(shè)備###
*/
fun connDev(dev: BluetoothDevice, bleConnCallBack: BleConnCallBack) {
closeConn()
mBleConnCallBack = bleConnCallBack
mBluetoothGatt = dev.connectGatt(mContext, false, mBluetoothGattCallback) // 連接藍(lán)牙設(shè)備
}
/**
* BLE中心設(shè)備連接外圍設(shè)備的數(shù)量有限(大概2~7個(gè)),在建立新連接之前必須釋放舊連接資源,否則容易出現(xiàn)連接錯(cuò)誤133
*/
fun closeConn() {
if (mBluetoothGatt != null) {
mBluetoothGatt!!.disconnect()
mBluetoothGatt!!.close()
}
}
private fun initBleClient() {
mBluetoothGattCallback = object : BluetoothGattCallback() {
/**
* 連接 狀態(tài)
*/
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val dev = gatt.device
AppLog.i(TAG, String.format("連接 狀態(tài)++onConnectionStateChange:%s,%s,%s,%s", dev.name, dev.address, status, newState))
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
isConnected = true
connTimes = 0
gatt.discoverServices() //啟動(dòng)服務(wù)發(fā)現(xiàn)
} else {
isConnected = false
closeConn()
if (connTimes++ < 5) {//錯(cuò)誤連接5次
dev.connectGatt(mContext, false, this)
} else {
connTimes = 0
Handler(Looper.getMainLooper()).post { mBleConnCallBack!!.onConntionedCallBack(status, isConnected) }
}
}
BleDevConnedState.saveBasicID(dev.address, isConnected)
AppLog.i(TAG,"isConnected : "+ isConnected+" status:"+status)
logTv(String.format(
if (status == 0)
if (newState == 2)
"與[%s]連接成功"
else "與[%s]連接斷開(kāi)"
else "與[%s]連接出錯(cuò),錯(cuò)誤碼:$status",
dev))
}
/**
* 發(fā)現(xiàn)服務(wù)
*/
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
AppLog.i(TAG, String.format("發(fā)現(xiàn)服務(wù)++onServicesDiscovered:%s,%s,%s", gatt.device.name, gatt.device.address, status))
if (status == BluetoothGatt.GATT_SUCCESS) { //BLE服務(wù)發(fā)現(xiàn)成功
Handler(Looper.getMainLooper()).post{setNotify()}
// 遍歷獲取BLE服務(wù)Services/Characteristics/Descriptors的全部UUID
for (service in gatt.services) {
val allUUIDs = StringBuilder("UUIDs={\nS=" + service.uuid.toString())
for (characteristic in service.characteristics) {
allUUIDs.append(",\nC=").append(characteristic.uuid)
for (descriptor in characteristic.descriptors)
allUUIDs.append(",\nD=").append(descriptor.uuid)
}
allUUIDs.append("}")
logTv("發(fā)現(xiàn)服務(wù)$allUUIDs")
}
}
}
/**
* 讀
*/
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
val uuid = characteristic.uuid
val valueStr = String(characteristic.value)
AppLog.i(TAG, String.format("onCharacteristicRead:%s,%s,%s,%s,%s", gatt.device.name, gatt.device.address, uuid, valueStr, status))
}
/**
* 寫入成功++
* @param gatt
* @param characteristic
* @param status
*/
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
val uuid = characteristic.uuid
val valueStr = String(characteristic.value)
AppLog.i(TAG, String.format("寫入成功++onCharacteristicWrite:%s,%s,%s,%s,%s,\n" +
" %s", gatt.device.name, gatt.device.address, uuid, valueStr, status, Arrays.toString(characteristic.value)))
onSendBytes()
}
/**
* 發(fā)過(guò)來(lái)
* 通知過(guò)來(lái)的消息++
* @param gatt
* @param characteristic
*/
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
val uuid = characteristic.uuid
val valueStr = String(characteristic.value)
AppLog.i(TAG, String.format("發(fā)過(guò)來(lái)++onCharacteristicChanged:%s,%s,%s,%s", gatt.device.name, gatt.device.address, uuid, valueStr))
onReadBytes(characteristic.value)
}
override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
val uuid = descriptor.uuid
val valueStr = Arrays.toString(descriptor.value)
AppLog.i(TAG, String.format("onDescriptorRead:%s,%s,%s,%s,%s", gatt.device.name, gatt.device.address, uuid, valueStr, status))
}
/**
* 寫入通知成功
*/
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
val uuid = descriptor.uuid
val valueStr = Arrays.toString(descriptor.value)
Handler(Looper.getMainLooper()).post {
mBluetoothGatt!!.requestMtu(256)//我們的是244
}
}
override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
super.onMtuChanged(gatt, mtu, status)
mMtu=mtu-3
AppLog.i(TAG,"連接成功"+"requestMtu"+"onMtuChanged:"+mtu+" "+status)
Handler(Looper.getMainLooper()).post {
mBleConnCallBack!!.onConntionedCallBack(status, isConnected)
}
}
}
}
5. 收發(fā)數(shù)據(jù)
與外圍設(shè)備交互經(jīng)常每次發(fā)的數(shù)據(jù)大于 mtu的,需要做分包處理,接收數(shù)據(jù)也要判斷數(shù)據(jù)的完整性最后才返回原數(shù)據(jù)做處理,所以一般交互最少包含包長(zhǎng)度,和包校驗(yàn)碼和原數(shù)據(jù)。當(dāng)然也可以加包頭,指令還有其他完整性校驗(yàn)。下面分享幾個(gè)公用方法:
/**
* 要可接收消息 需要要打開(kāi)通知###
*/
fun setNotify() {
val service = getGattService(UUID_SERVICE)
if (service != null) {
// 設(shè)置Characteristic通知
val characteristic = service.getCharacteristic(UUID_CHAR_READ_NOTIFY)//通過(guò)UUID獲取可通知的Characteristic
mBluetoothGatt!!.setCharacteristicNotification(characteristic, true)
// 向Characteristic的Descriptor屬性寫入通知開(kāi)關(guān),使藍(lán)牙設(shè)備主動(dòng)向手機(jī)發(fā)送數(shù)據(jù)
val descriptor = characteristic.getDescriptor(UUID_DESC_NOTITY)
// descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);//和通知類似,但服務(wù)端不主動(dòng)發(fā)數(shù)據(jù),只指示客戶端讀取數(shù)據(jù)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE//1,0
mBluetoothGatt!!.writeDescriptor(descriptor)
}
}
//數(shù)據(jù)分包處理
private fun splitPacketForMtuByte(data: ByteArray?): Queue<ByteArray> {
val dataInfoQueue = LinkedList<ByteArray>()
if (data != null) {
var index = 0
do {
val currentData: ByteArray
if (data.size - index <= mMtu) {
currentData = ByteArray(data.size - index)
System.arraycopy(data, index, currentData, 0, data.size - index)
index = data.size
} else {
currentData = ByteArray(mMtu)
System.arraycopy(data, index, currentData, 0, mMtu)//從零開(kāi)始復(fù)制++ mMtu
index += mMtu
}
dataInfoQueue.offer(currentData)
} while (index < data.size)
}
return dataInfoQueue
}
private fun intToByteArray(a: Int): ByteArray =
byteArrayOf((a shr 24 and 0xFF).toByte(),
(a shr 16 and 0xFF).toByte(),
(a shr 8 and 0xFF).toByte(),
(a and 0xFF).toByte())
private fun bytes2Int(byteArray: ByteArray): Int =
(byteArray[0].toInt() and 0xFF shl 24) or
(byteArray[1].toInt() and 0xFF shl 16) or
(byteArray[2].toInt() and 0xFF shl 8) or
(byteArray[3].toInt() and 0xFF)
/**
* 寫數(shù)據(jù)###
* @param instruction 指令
* @param jsonData json數(shù)據(jù)
* @param bleServerCallBack 接收完Basic 數(shù)據(jù)后響應(yīng)
*/
fun write(instruction: Byte, jsonData: String, bleServerCallBack: BleServerCallBack) {
val service = getGattService(UUID_SERVICE)
if (service != null) {
// ....
var bleData = byteMergerAll(PACKAGE_HEAD, intToByteArray, instructionArray,crc32ByteArray, jsonData.toByteArray())
bleData = byteMergerAll(
//...頭,length,指令等
jsonData.toByteArray(),
getShaHead(bleData)) //可以用hash校驗(yàn)數(shù)據(jù)完整性
mBleServerCallBack = bleServerCallBack
resultSize = 0
mCharacteristic = service.getCharacteristic(UUID_CHAR_WRITE)//通過(guò)UUID獲取可寫的Characteristic
writeCharacteristic(mCharacteristic, bleData)
}
}
/**
* 向characteristic寫數(shù)據(jù)
*
* @param value
*/
private fun writeCharacteristic(characteristic: BluetoothGattCharacteristic?, value: ByteArray) {
this.mCharacteristic = characteristic
if (dataInfoQueue != null) {
dataInfoQueue!!.clear()
writeSize = value.size//這是我自己寫的讀寫進(jìn)度用的
writeProgress = 0
dataInfoQueue = splitPacketForMtuByte(value)
onSendBytes()
}
}
/**
* 連續(xù)發(fā)送
* 每次從隊(duì)列里取第一個(gè)同時(shí)移除
*/
private fun onSendBytes() {
if (dataInfoQueue != null && !dataInfoQueue!!.isEmpty()) {
//檢測(cè)到發(fā)送數(shù)據(jù),直接發(fā)送
if (dataInfoQueue!!.peek() != null) {
mCharacteristic!!.value = dataInfoQueue!!.poll()//移除并返回隊(duì)列頭部的元素
writeProgress += mCharacteristic!!.value.size
mBleServerCallBack!!.onWriteProgress(writeProgress * 100 / writeSize)
Handler(Looper.getMainLooper()).postDelayed({
mBluetoothGatt!!.writeCharacteristic(mCharacteristic)
}, 125)//120ms
}
}
}
/**
* 寫入成功++
* @param gatt
* @param characteristic
* @param status
*/
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
val uuid = characteristic.uuid
val valueStr = String(characteristic.value)
AppLog.i(TAG, String.format("寫入成功++onCharacteristicWrite:%s,%s,%s,%s,%s,\n" +
" %s", gatt.device.name, gatt.device.address, uuid, valueStr, status, Arrays.toString(characteristic.value)))
onSendBytes()//繼續(xù)發(fā)送
}
/**
* 發(fā)過(guò)來(lái)
* 通知過(guò)來(lái)的消息++
* @param gatt
* @param characteristic
*/
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
val uuid = characteristic.uuid
val valueStr = String(characteristic.value)
AppLog.i(TAG, String.format("發(fā)過(guò)來(lái)++onCharacteristicChanged:%s,%s,%s,%s", gatt.device.name, gatt.device.address, uuid, valueStr))
onReadBytes(characteristic.value) //處理發(fā)回來(lái)的數(shù)據(jù),最后校驗(yàn)拼接好
}
- 上面的設(shè)置通知和寫數(shù)據(jù)都要 用到 mBluetoothGatt的 服務(wù),特征
- 主動(dòng)讀取和通知 會(huì)用到 特征里的
描述詳見(jiàn)setNotify() 和 write()
而操作服務(wù),特征,描述 都通過(guò)特定的UUIU來(lái)操作,這些uuid都是藍(lán)牙設(shè)備從生產(chǎn)出來(lái)就不變了, 兩手機(jī)調(diào)試時(shí)可以自定義uuid.
val UUID_SERVICE =UUID.fromString("00000000-0000-0000-0000-0000000000f0")//自定義UUID
val UUID_CHAR_READ_NOTIFY = UUID.fromString("00000000-0000-0000-0000-0000000000f2")
val UUID_CHAR_WRITE = UUID.fromString("00000000-0000-0000-0000-000000000001")
val UUID_DESC_NOTITY = UUID.fromString("00000000-0000-0000-0000-000000000002")
我自己封裝的一個(gè)BleUtil ,因?yàn)樯婕案緲I(yè)務(wù)關(guān)聯(lián)性太強(qiáng)(主要是傳輸包的協(xié)議不同)就先不開(kāi)源出來(lái)了,如果這邊文章對(duì)大家有幫助反饋不錯(cuò),我會(huì)考慮上傳個(gè)demo到github供大家使用,
在這先給大家推薦一個(gè)不錯(cuò) Demo,里面除了沒(méi)有分包,協(xié)議,和傳輸速率?;镜墓δ芏加校艺{(diào)試數(shù)據(jù)到打印到界面上了。最主要是它可以用兩個(gè)個(gè)手機(jī)一個(gè)當(dāng)中心設(shè)備一個(gè)當(dāng)外圍設(shè)備調(diào)試。
如果在實(shí)際使用藍(lán)牙, 調(diào)用時(shí)得姿勢(shì)應(yīng)該是這樣的
BleUtils.getInstance(mContext)
.write(
BleInstructions.signTx,
jsonObject.toString(),
object : BleServerCallBack {
override fun onResponse(instruction: Byte?, result: String) {
val fromJson = Gson().fromJson(result, xxxBean::class.java)
}
override fun onWriteProgress(progress: Int) {
}
override fun onReadProgress(progress: Int) {
}
})
6. 傳輸速率
首先傳輸速率優(yōu)化有兩個(gè)方向,1 外圍設(shè)備傳輸?shù)紸ndroid 。2 Android傳輸?shù)酵鈬O(shè)備。
我在開(kāi)發(fā)中首先先使用上面那位仁兄的demo調(diào)試,兩個(gè)Android 設(shè)備調(diào)試不延時(shí),上一個(gè)成功馬上下一個(gè),最多一秒發(fā)11個(gè)20字節(jié)的包。
后來(lái)和我們的藍(lán)牙設(shè)備調(diào)試時(shí)發(fā)現(xiàn)發(fā)送特別快,但是數(shù)據(jù)不完整,他藍(lán)牙模塊接收成功了,但是透?jìng)鲾?shù)據(jù)到芯片處理時(shí)發(fā)現(xiàn)不完整,我們的硬件小伙伴說(shuō)因?yàn)?a target="_blank">波特率限制(差不多每10字節(jié)透?jìng)饕臅r(shí)1ms)和藍(lán)牙模塊的buff (打印時(shí)是最多100byte,100打印的)限制,就算藍(lán)牙模塊每包都告訴你接收成功,也是沒(méi)透?jìng)魍昃陀纸邮樟?。后?lái)通過(guò)調(diào)試每次發(fā)20K數(shù)據(jù),最后是 Android 發(fā)是 20字節(jié)/130ms穩(wěn)定。給Android 發(fā)是20字節(jié)/ 8ms。 (天殺的20字節(jié),網(wǎng)上都是說(shuō)20字節(jié)最多了)
后來(lái)看了國(guó)外一家物聯(lián)網(wǎng)公司總結(jié)的 Ble 吞吐量的文章(上面有連接),知道Android 每個(gè)延時(shí)是可以連續(xù)接收6個(gè)包的。就改為 120字節(jié)/ 16ms (為啥是16ms,不是每次間隔要6個(gè)包嗎,怎么像間隔兩次,這時(shí)因?yàn)椴ㄌ芈视绊?,多?個(gè)包100字節(jié),差不多 我們的單片機(jī)透?jìng)鞯剿{(lán)牙模塊要多耗時(shí)不到10ms )
而Android 發(fā)數(shù)據(jù)可以申請(qǐng) 我們?cè)O(shè)備的mtu 來(lái)得到最多每次能發(fā)多少字節(jié)。延時(shí)還是130ms,即:241字節(jié)/ 130ms 提高12倍,這個(gè)速度還可以。
7. 擴(kuò)展
BLE的傳輸速率分析
根據(jù)藍(lán)牙BLE協(xié)議, 物理層physical layer的傳輸速率是1Mbps,相當(dāng)于每秒125K字節(jié)。事實(shí)上,其只是基準(zhǔn)傳輸速率,協(xié)議規(guī)定BLE不能連續(xù)不斷地傳輸數(shù)據(jù)包,否則就不能稱為低功耗藍(lán)牙了。連續(xù)傳輸自然會(huì)帶來(lái)高功耗。所以,藍(lán)牙的最高傳輸速率并不由物理層的工作頻率決定的。
在實(shí)際的操作過(guò)程中,如果主機(jī)連線不斷地發(fā)送數(shù)據(jù)包,要么丟包嚴(yán)重要么連接出現(xiàn)異常而斷開(kāi)。
在BLE里面,傳輸速度受其連接參數(shù)所影響。連接參數(shù)定義如下:
1)連接間隔。藍(lán)牙基帶是跳頻工作的,主機(jī)和從機(jī)會(huì)商定多長(zhǎng)時(shí)間進(jìn)行跳頻連接,連接上才能進(jìn)行數(shù)據(jù)傳輸。這個(gè)連接和廣播狀態(tài)和連接狀態(tài)的連接不是一樣的意思。主機(jī)在從機(jī)廣播時(shí)進(jìn)行連接是應(yīng)用層的主動(dòng)軟件行為。而跳頻過(guò)程中的連接是藍(lán)牙基帶協(xié)議的規(guī)定,完全由硬件控制,對(duì)應(yīng)用層透明。明顯,如果這個(gè)連接間隔時(shí)間越短,那么傳輸?shù)乃俣染驮龃?。連接上傳完數(shù)據(jù)后,藍(lán)牙基帶即進(jìn)入休眠狀態(tài),保證低功耗。其是1.25毫秒一個(gè)單位。
2)連接延遲。其是為了低功耗考慮,允許從機(jī)在跳頻過(guò)程中不理會(huì)主機(jī)的跳頻指令,繼續(xù)睡眠一段時(shí)間。而主機(jī)不能因?yàn)閺臋C(jī)睡眠而認(rèn)為其斷開(kāi)連接了。其是1.25毫秒一個(gè)單位。明顯,這個(gè)數(shù)值越小,傳輸速度也高。
藍(lán)牙BLE協(xié)議規(guī)定連接參數(shù)最小是5,即7.25毫秒;而Android手機(jī)規(guī)定連接參數(shù)最小是8,即10毫秒。iOS規(guī)定是16,即20毫秒。
連接參數(shù)完全由主機(jī)決定,但從機(jī)可以發(fā)出更新參數(shù)申請(qǐng),主機(jī)可以接受也可以拒絕。android手機(jī)一部接受,而ios比較嚴(yán)格,拒絕的概率比較高。
