藍(lán)牙開發(fā)之ble

ble的概念以及原理的簡(jiǎn)單理解

藍(lán)牙是一種短距離無(wú)線通信技術(shù),而藍(lán)牙低功耗(BLE)是在藍(lán)牙4.0協(xié)議上修改以適用低功耗應(yīng)用場(chǎng)景的一種藍(lán)牙協(xié)議。
那么4.0以后的藍(lán)牙為什么低功耗了呢?傳統(tǒng)藍(lán)牙是通過(guò)廣播收發(fā)狀態(tài),連接建立后通過(guò)socket建立連接,交互數(shù)據(jù)
ble相對(duì)于傳統(tǒng)藍(lán)牙

  • 廣播頻段和廣播時(shí)射頻開啟時(shí)間的減少:傳統(tǒng)藍(lán)牙使用16~32個(gè)頻段進(jìn)行廣播,而BLE僅使用3個(gè)廣播頻段; 每次廣播時(shí)的射頻
    開啟時(shí)間由傳統(tǒng)藍(lán)牙的22ms減少為0.6~1.2ms;
  • BLE設(shè)計(jì)了深度睡眠狀態(tài)(Duty-Cycle)來(lái)替代傳統(tǒng)藍(lán)牙的空閑時(shí)間,并且在Duty-Cycle時(shí),發(fā)送數(shù)據(jù)間隔也被增大.
  • BLE 的連接采用先進(jìn)的Sniffer-Subrating模式.
  • 傳統(tǒng)藍(lán)牙規(guī)范規(guī)定,若某一設(shè)備正在進(jìn)行廣播,則它不會(huì)響應(yīng)當(dāng)前正在進(jìn)行的設(shè)備掃描,而BLE允許正在進(jìn)行廣播的設(shè)備連接正
    在掃描的設(shè)備,這有效避免了重復(fù)連接。通過(guò)對(duì)連接機(jī)制的改進(jìn),BLE連接建立過(guò)程可控制在3ms內(nèi)完成。
  • BLE在每個(gè)從設(shè)備和每個(gè)數(shù)據(jù)包上使用32位的存取地址,優(yōu)化了傳統(tǒng)藍(lán)牙一對(duì)一的連接,實(shí)現(xiàn)一對(duì)多(目前測(cè)試的是1帶6)。
  • BLE增加了GFSK調(diào)制,降低峰值功耗。
    以上參考了https://blog.csdn.net/ZQ07506149/article/details/82380509

什么是GATT?

通用屬性配置文件層(Generic Attribute profile,簡(jiǎn)寫 GATT),簡(jiǎn)單點(diǎn)就是ble應(yīng)用運(yùn)行的環(huán)境或者場(chǎng)景,官方的專業(yè)解釋,個(gè)人實(shí)在懶的貼出來(lái),而且覺得也不好理解

GATT的層次結(jié)構(gòu)

GATT通常有一個(gè)或者多個(gè)“Services”組成,一個(gè) Characteristic 可以包含若干 Descriptor和value。而 Characteristic 定義了數(shù)值和操作。Characteristic 的操作這幾種權(quán)限:讀、寫、通知等權(quán)限。

uuid

Service、Characteristic 還有 Descriptor 都是使用 UUID 唯一標(biāo)示的。一會(huì)你可以看到在實(shí)際的操作中,都是通過(guò)uuid來(lái)查找你對(duì)應(yīng)的設(shè)備

ble的開發(fā)

ble也是C/S的開發(fā)模式,特別注意一點(diǎn)ble開發(fā)要在主線程(底層源碼注釋),所以當(dāng)兩個(gè)設(shè)備建立連接之后,它們就處于下面兩種角色之一:

  • GATT服務(wù)器:為GATT客戶端提供數(shù)據(jù)服務(wù)的設(shè)備。外圍設(shè)備 可以創(chuàng)建uuid的服務(wù)
  • GATT客戶端:從GATT服務(wù)器讀寫應(yīng)用數(shù)據(jù)的設(shè)備。中央設(shè)備

android的支持版本

官方說(shuō)從android4.3版本開始,就支持了ble,但是在實(shí)際測(cè)試可不是這樣,國(guó)內(nèi)好多機(jī)型開始支持的版本號(hào)都不一樣啊,這也是一個(gè)android碎片化的體現(xiàn)

GATT的連接問(wèn)題

GATT連接是獨(dú)占的。外設(shè)只能建立一個(gè)連接,但是客戶端可以連接多臺(tái)設(shè)備

GATT通信

中央設(shè)備在給外設(shè)發(fā)消息的時(shí)候,有時(shí)候只能發(fā)送一次, 因?yàn)閷懱卣髦登翱梢栽O(shè)置寫的類型setWriteType(),寫類型有三種,如下:

  • WRITE_TYPE_DEFAULT 默認(rèn)類型,需要外圍設(shè)備的確認(rèn),也就是需要外圍設(shè)備的回應(yīng),這樣才能繼續(xù)發(fā)送寫。
  • WRITE_TYPE_NO_RESPONSE 設(shè)置該類型不需要外圍設(shè)備的回應(yīng),可以繼續(xù)寫數(shù)據(jù)。加快傳輸速率。
  • WRITE_TYPE_SIGNED 寫特征攜帶認(rèn)證簽名,具體作用不太清楚。

適配ios

在開發(fā)ble服務(wù)端的時(shí)候,遇到過(guò)一個(gè)很坑的問(wèn)題,調(diào)試了很久,需要在Characteristic特征中加入以下這個(gè)BluetoothGattCharacteristic.PROPERTY_NOTIFY,否則ios接收不到,android沒(méi)事,不知道ios的ble模塊是怎樣實(shí)現(xiàn)的,有知道可以留言一下

Beacon

非連接型的ble的數(shù)據(jù)收發(fā)

外圍設(shè)備代碼開發(fā)

初始化

 mBluetoothLeAdvertiser.startAdvertising(createAdvSettings(), createAdvData(), mAdvertiseCallback);

廣告模式的回調(diào)

  private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            super.onStartSuccess(settingsInEffect);
            Log.d(TAG, "onStartSuccess");
        }

        @Override
        public void onStartFailure(int errorCode) {
            super.onStartFailure(errorCode);
            Log.d(TAG, "onStartFailure " + errorCode);
        }
    };

設(shè)置廣播數(shù)據(jù)

private AdvertiseSettings createAdvSettings() {
AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder();
    //手機(jī)的發(fā)射功率,簡(jiǎn)單說(shuō)就是藍(lán)牙走多遠(yuǎn)
    builder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
    builder.setConnectable(true);
    builder.setTimeout(0);
    //ADVERTISE_MODE_LOW_POWER 在低功耗模式下執(zhí)行藍(lán)牙LE廣告。這是默認(rèn)和首選的廣告模式,因?yàn)樗淖钌俚碾娏Α?    //ADVERTISE_MODE_BALANCED 在平衡電源模式下執(zhí)行藍(lán)牙LE廣告。這是廣告頻率和功耗之間的平衡。
    //ADVERTISE_MODE_LOW_LATENCY 在低延遲,高功率模式下執(zhí)行藍(lán)牙LE廣告。這是最高的功耗,不應(yīng)該用于連續(xù)的背景廣告。
    // TODO: 18-1-7 測(cè)試模式的值
    builder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED);
    return builder.build();
}

設(shè)置響應(yīng)數(shù)據(jù)

private AdvertiseData createAdvData() {
    AdvertiseData.Builder builder = new AdvertiseData.Builder();
    builder.addServiceUuid(ParcelUuid.fromString(UUID_SAMPLE_NAME_SERVICE));
    //這樣即使定義service uuid跟別人的有沖突,也可以在中心過(guò)濾該magic number來(lái)找到符合自己需求的外圍設(shè)備,,但目前x是以設(shè)備名+系列號(hào)
    byte mLeManufacturerData[] = {(byte) 0x4C, (byte) 0x00, (byte) 0x02, (byte) 0x15, (byte) 0x15, (byte) 0x15, (byte) 0x15};
    builder.addManufacturerData(0x3103 + 1, mLeManufacturerData);
    builder.setIncludeTxPowerLevel(false);
    builder.setIncludeDeviceName(true);
    return builder.build();
}

初始化GATT服務(wù)

//初始化GATT服務(wù)
BluetoothGattService nameService = new BluetoothGattService(UUID.fromString(UUID_SAMPLE_NAME_SERVICE),
BluetoothGattService.SERVICE_TYPE_PRIMARY);
//增加讀寫特征
mBleGattCharacteristicWrite = new BluetoothGattCharacteristic(
UUID.fromString(UUID_SAMPLE_NAME_CHARACTERISTIC),
BluetoothGattCharacteristic.PROPERTY_READ
| BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_READ
| BluetoothGattCharacteristic.PERMISSION_WRITE);
nameService.addCharacteristic(mBleGattCharacteristicWrite);
mGattServer.addService(nameService);

GATT服務(wù)的回調(diào)

mGattServer = mBluetoothManager.openGattServer(mContext, new BluetoothGattServerCallback() {
            @Override
            public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
                super.onConnectionStateChange(device, status, newState);
                {
                    switch (newState) {
                        case BluetoothProfile.STATE_DISCONNECTED:
                            Log.d(TAG, device.getName() + " 斷開");
                            sendHandlerMsg(device.getName() + " 斷開");
                            break;
                        case BluetoothProfile.STATE_CONNECTED:
                            Log.d(TAG, device.getName() + " 連接");
                            sendHandlerMsg(device.getName() + " 連接");
                            break;
                        default:
                            break;
                    }
                }
            }

            @Override
            public void onServiceAdded(int status, BluetoothGattService service) {
                super.onServiceAdded(status, service);
                Log.d(TAG, "onServiceAdded");
            }

            /**A remote client has requested to read a local characteristic.*/
            @Override
            public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
                super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
                Log.d(TAG, "read");
            }

            /**
             * 接收消息的方法 - 接收具體的字節(jié)
             */
            @Override
            public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
                super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
                characteristic.setValue(mReceiveSuccess.getBytes());
                //觸發(fā)發(fā)消息給客戶端
                mGattServer.notifyCharacteristicChanged(device,characteristic,false);
                String msg = new String(value);
                Log.d(TAG, "write " + msg);
                sendHandlerMsg(msg);
                //.處理響應(yīng)內(nèi)容
                //onResponseToClient(requestBytes, device, requestId, characteristic);
            }

            /**
             *  特征被讀取。當(dāng)回復(fù)響應(yīng)成功后,客戶端會(huì)讀取然后觸發(fā)本方法
             *
             */
            @Override
            public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
                super.onDescriptorReadRequest(device, requestId, offset, descriptor);
                Log.d(TAG, "DescriptorRead");
                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
            }

            /**
             * 2.描述被寫入時(shí)
             */
            @Override
            public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
                super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
                Log.d(TAG, "DescriptorWrite");
                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
            }

            @Override
            public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
                super.onExecuteWrite(device, requestId, execute);
                Log.d(TAG, "onExecuteWrite");
            }

            @Override
            public void onNotificationSent(BluetoothDevice device, int status) {
                super.onNotificationSent(device, status);
                Log.d(TAG, "onNotificationSent");
            }
        });

客戶端代碼 - 中央設(shè)備

初始化-掃描-連接-連接GATT服務(wù)-搜索服務(wù)-通信

檢查藍(lán)牙打開,ble可用,得到BluetoothAdapter對(duì)象

掃描

該方法專門針對(duì)ble的掃描設(shè)備,不是傳統(tǒng)掃描,無(wú)法掃描非ble外設(shè),api要求21以上

  mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
  mBluetoothLeScanner.startScan(mScanCallback);
  
  private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            if (null != result) {
                BluetoothDevice device = result.getDevice();
            
            }

        }
    };

連接

public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
    return false;
}
//根據(jù)地址連接遠(yuǎn)程設(shè)備
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
        return false;
}
//連接已開啟的GATT服務(wù)
mBluetoothGatt = device.connectGatt(mContext, false, mGattCallback);
mBluetoothDeviceAddress = address;

GATT服務(wù)的回調(diào),成功連接后開始搜索服務(wù) mBluetoothGatt.discoverServices();

private  BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        /**
         * 連接狀態(tài)的改變
         */
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            //成功連接后開始搜索服務(wù)
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                discoverServices();
                setConnectionStatus(STATE_CONNECTED);
                if (null != mIBleGattCallback) {
                    mIBleGattCallback.onConnectionStateChange(gatt, mConnectionState);
                }
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                setConnectionStatus(STATE_DISCONNECTED);
                if (null != mIBleGattCallback) {
                    mIBleGattCallback.onConnectionStateChange(gatt, mConnectionState);
                }
            }
        }

       /**
         * 發(fā)現(xiàn)服務(wù)
         */
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            Log.d(TAG,"onServicesDiscovered");
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (null != mIBleGattCallback) {
                    mIBleGattCallback.onServicesDiscovered(gatt);
                }
            } else {
                excuteError(status);
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt,
                                         BluetoothGattCharacteristic characteristic,
                                         int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (null != mIBleGattCallback) {
                    mIBleGattCallback.onCharacteristicRead(gatt, characteristic);
                }
            } else {
                excuteError(status);
            }
        }

        
         /**
         * 接收外設(shè)的消息
         * @param gatt           BluetoothProfile的對(duì)象
         * @param characteristic 特征
         */
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            if (null != mIBleGattCallback) {
                mIBleGattCallback.onCharacteristicChanged(gatt, characteristic);
                onReceiveMsg(characteristic);
            }
        }
    };

發(fā)數(shù)據(jù)

獲取可用的特征
可以通過(guò)協(xié)定好的uuid獲取,也可以遍歷獲取可寫的服務(wù)

//判斷特征可寫
public boolean ifCharacteristicWritable(BluetoothGattCharacteristic characteristic){
    return ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 ||
            (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0);
}
//直接獲取相應(yīng)uuid的服務(wù)
UUID uuid = UUID.fromString(BleStatusConstant.UUID_SAMPLE_NAME_SERVICE);
BluetoothGattService service = gatt.getService(uuid);
UUID uuid2 = UUID.fromString(BleStatusConstant.UUID_SAMPLE_NAME_CHARACTERISTIC);
mBleCharacteristic = service.getCharacteristic(uuid2);

寫入屬性

/**
 * 寫入屬性
 *
 * @param characteristic 特征
 * @return true寫入成功 false寫入失敗
 */
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
    if (mBluetoothAdapter == null || mBluetoothGatt == null) {
        return false;
    }
    return mBluetoothGatt.writeCharacteristic(characteristic);
}

結(jié)束別忘了釋放,如果有掃描也需要停止掃描

if (mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.close();
        mBluetoothGatt = null;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容