Android BLE低功耗藍(lán)牙開(kāi)發(fā)

最近做了一個(gè)智能硬件開(kāi)發(fā)(針灸儀)的項(xiàng)目她按,有一部分涉及到低功耗藍(lán)牙的開(kāi)發(fā)渠旁,就是通過(guò)藍(lán)牙和設(shè)備進(jìn)行數(shù)據(jù)的交互风响,比如控制改設(shè)備的LED的開(kāi)關(guān),設(shè)備的開(kāi)關(guān)機(jī)羡亩,設(shè)置設(shè)備的時(shí)間和溫度等摩疑,下面就項(xiàng)目中遇到的坑一一說(shuō)明:首先給出官網(wǎng)對(duì)于BLE開(kāi)發(fā)的講解,https://developer.Android.com/guide/topics/connectivity/bluetooth-le.html#terms官方demo:https://github.com/googlesamples/android-BluetoothLeGatt,demo也比較好理解畏铆,主要是四個(gè)類雷袋,其中,DeviceControlActivity通過(guò)啟動(dòng)BluetoothLeService用來(lái)進(jìn)行與藍(lán)牙外圍設(shè)備的交互辞居。(注意楷怒,因?yàn)楸救耸菍I做了更改,并放到了fragment中瓦灶,所以部分代碼跟demo不一致鸠删,請(qǐng)主動(dòng)忽略,關(guān)注藍(lán)牙核心代碼)

BLE開(kāi)發(fā)所需要的知識(shí)倚搬,通過(guò)官方demo冶共,我們會(huì)發(fā)現(xiàn)很多service,點(diǎn)擊service后每界,每個(gè)service下面是Characteristic,每個(gè)service和Characteristic都對(duì)應(yīng)一個(gè)唯一的UUID家卖。所以眨层,在做BLE時(shí)候,首先你應(yīng)該找出你的藍(lán)牙外圍設(shè)備uuid上荡,不然會(huì)很頭疼趴樱,這個(gè)UUID也可能是硬件給你的,也可以你自己試出來(lái)酪捡,當(dāng)然自己試出來(lái)是個(gè)很煩的過(guò)程叁征。自己試的方法就是根據(jù)demo,加上一份讀寫的協(xié)議逛薇,然后捺疼,排著點(diǎn)擊,顯示出來(lái)的藍(lán)牙列表進(jìn)行測(cè)試永罚,看是否和協(xié)議對(duì)應(yīng)啤呼。另外,BluetoothLeService類不用做太多的更改呢袱。

一官扣,藍(lán)牙設(shè)備的掃描

這一部分基本上很簡(jiǎn)單,只要設(shè)備上電以后羞福,這部分代碼執(zhí)行后惕蹄,便可以掃描出設(shè)備,并獲得BluetoothDevice對(duì)象

    public void scanLeDevice(final boolean enable) {
        LogUtils.debug(TAG, "-----------開(kāi)始掃描藍(lán)牙=" + enable);
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    stopScanOuter();
                }
            }, SCAN_PERIOD);
            LogUtils.debug("----------startLeScan--");
            mScanning = true;
            mBluetoothAdapter.startLeScan(this);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(this);
        }
    }

上面代碼為開(kāi)始掃描周圍已上電的設(shè)備,當(dāng)發(fā)現(xiàn)設(shè)備后卖陵,BluetoothAdapter.LeScanCallback會(huì)執(zhí)行onLeScan回調(diào)恋昼,將BluetoothDevice返回,

    @Override
    public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
        Fragment dcfrag = getActivity().getSupportFragmentManager().findFragmentByTag("MyDeviceFragment");
        if(dcfrag != null && dcfrag.isVisible()) {
            getActivity().runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    LogUtils.debug(TAG, "---------獲得設(shè)備" + device);
                    mLeDeviceListAdapter.addDevice(device);
                    mLeDeviceListAdapter.notifyDataSetChanged();
                }
            });
        }
    }

到此赶促,我們便可以得到掃描到的藍(lán)牙設(shè)備液肌,但是目前僅僅是掃描到,并不代表已經(jīng)連接上藍(lán)牙設(shè)備鸥滨。

二嗦哆,藍(lán)牙設(shè)備的連接

1,綁定service

    private final ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder service) {
            LogUtils.debug(TAG, "開(kāi)始綁定service onServiceConnected"+device+"---name="+device.getName()+"--address="+device.getAddress());
            mDeviceAddress = device.getAddress();
            mDeviceName = device.getName();

            mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
            if (!mBluetoothLeService.initialize()) {
                Log.i(TAG, "Unable to initialize Bluetooth");
            }
            // Automatically connects to the device upon successful start-up initialization.
            mBluetoothLeService.connect(mDeviceAddress);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            LogUtils.debug(TAG, "--------onServiceDisconnected service無(wú)法綁定了");
            mBluetoothLeService = null;
        }
    };
    
    
    public void mybindService(){
        LogUtils.debug(TAG, "---------開(kāi)始執(zhí)行onCreate---bindservice");
        Intent gattServiceIntent = new Intent(getActivity(), BluetoothLeService.class);
        getActivity().bindService(gattServiceIntent, mServiceConnection, getActivity().BIND_AUTO_CREATE);
    }

2婿滓,連接藍(lán)牙

    private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
                LogUtils.debug(TAG, "--mGattUpdateReceiver  ACTION_GATT_CONNECTED");
                mConnected = true;

                LogUtils.debug(TAG, "--------ACTION_GATT_CONNECTED devicename"+mDeviceName);
                Fragment dcfrag = getActivity().getSupportFragmentManager().findFragmentByTag("ConnectFragment");
                if((dcfrag != null && dcfrag.isVisible())){

                    //暫時(shí)寫死在這可以連接的BLE設(shè)備
                    if(mDeviceName == null || !deviceFilter(mDeviceName)){
                        connectService.switchFragment(false, mDeviceName);
                    }else{
                        connectService.switchFragment(true, mDeviceName);
                    }
                }

            } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
                LogUtils.debug(TAG, "--mGattUpdateReceiver  ACTION_GATT_DISCONNECTED");
                mConnected = false;
                Fragment dcfrag = getActivity().getSupportFragmentManager().findFragmentByTag("ConnectFragment");
                if((dcfrag != null && dcfrag.isVisible()))
                    connectService.switchFragment(false, mDeviceName);

                Fragment contfrag = getActivity().getSupportFragmentManager().findFragmentByTag("ControlorFragment");
                if((contfrag != null && contfrag.isVisible())){
                    transfertoControler.closeButton(false, false);
                }
            } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
                LogUtils.debug(TAG, "--mGattUpdateReceiver  ACTION_GATT_SERVICES_DISCOVERED");
                initMoxibustionService(
                        mBluetoothLeService.getSupportedGatteService(
                                SampleGattAttributes.SERVIECE_NOTIFY_DATA));

                initMoxibustionService(
                        mBluetoothLeService.getSupportedGatteService(
                                SampleGattAttributes.SERVIECE_WRITE_DATA));

                // Show all the supported services and characteristics on the user interface.
//                displayGattServices(mBluetoothLeService.getSupportedGattServices());
            } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
                LogUtils.debug(TAG, "--mGattUpdateReceiver  ACTION_DATA_AVAILABLE");
//                byte[] data = intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA);
//                StringBuilder stringBuilder = new StringBuilder(data.length);
//                for(byte byteChar : data)
//                    stringBuilder.append(String.format("0x%02X ", byteChar));
//                String log = stringBuilder.toString();
//                LogUtils.debug(TAG, "---字節(jié)數(shù)組為="+ log);
                parsedata(intent.getByteArrayExtra(BluetoothLeService.EXTRA_DATA));
            }  else if (BluetoothLeService.ACTION_DATA_SEND_CONFIRM.equals(action)) {
                // ECHO from android
                LogUtils.debug(TAG, "write ok!");
            }


        }
    };
    public void myConnetService(){
        getActivity().registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
        LogUtils.debug(TAG, "------mBluetoothLeService  myConnetService" + mBluetoothLeService);
        if (mBluetoothLeService != null) {
            final boolean result = mBluetoothLeService.connect(mDeviceAddress);
            LogUtils.debug(TAG, "Connect request result=" + result);
        }
    }

當(dāng)連接上藍(lán)牙后老速,我們會(huì)得到ACTION_GATT_CONNECTED的廣播,然后是ACTION_GATT_SERVICES_DISCOVERED凸主,這個(gè)時(shí)候我們需要對(duì)service進(jìn)行初始化橘券,以便能夠讀寫數(shù)據(jù),以下為初始化代碼(注意卿吐,初始化時(shí)候我們需要用到讀寫service的UUID)

    private void initMoxibustionService(BluetoothGattService gattService) {
        String uuid = "";
        if (gattService == null)
        {
            LogUtils.debug(TAG, "gattService is null");
            return;
        }
        List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
        for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
            uuid = gattCharacteristic.getUuid().toString();
            if (SampleGattAttributes.CHARACTER_NOTIFY_DATA.substring(0,8).equals(uuid.substring(0, 8))) {
                mNotifyCharacteristic = gattCharacteristic;
                mBluetoothLeService.setCharacteristicNotification(
                        mNotifyCharacteristic, true);
                LogUtils.debug(TAG, "NOTIFY_DATA");
                LogUtils.debug(TAG, "getProperties()=" + mNotifyCharacteristic.getProperties());
            } else if (SampleGattAttributes.CHARACTER_WRITE_DATA.substring(0,8).equals(uuid.subSequence(0, 8))) {
//                mCommandCharacteristic = gattCharacteristic;

                //寫數(shù)據(jù)的服務(wù)和characteristic
                mCommandCharacteristic = mBluetoothLeService.getSupportedGatteService(SampleGattAttributes.SERVIECE_WRITE_DATA)
                        .getCharacteristic(UUID.fromString(SampleGattAttributes.CHARACTER_WRITE_DATA));


                LogUtils.debug(TAG, "WRITE_CMD");
                LogUtils.debug(TAG, "getProperties()=" + mCommandCharacteristic.getProperties());
                mCommandCharacteristic.setWriteType(
                        BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
                LogUtils.debug(TAG, "getProperties()=" + mCommandCharacteristic.getProperties());
            }
        }
    }

CHARACTER_NOTIFY_DATA和CHARACTER_WRITE_DATA為讀和寫數(shù)據(jù)的CHARACTER的UUID旁舰,如下,注意這四個(gè)UUID

3嗡官,解析數(shù)據(jù)

至此箭窜,如果順利的話,我們就可以得到ACTION_DATA_AVAILABLE的廣播衍腥,也就拿到了從藍(lán)牙設(shè)備獲得的byte數(shù)組磺樱,大多數(shù)協(xié)議里,每個(gè)字節(jié)代表一個(gè)命令婆咸。這里涉及到Java中byte值與int值的轉(zhuǎn)換竹捉。因?yàn)镴ava中,所有的值都是singed性的尚骄,最高位為符號(hào)位块差,所以,大家請(qǐng)自行補(bǔ)下該部分的知識(shí)乖仇,對(duì)于有符號(hào)數(shù)憾儒,它的值相當(dāng)于取補(bǔ)碼,此處將不詳述乃沙,

上面為讀數(shù)據(jù)起趾,下面我們說(shuō)寫數(shù)據(jù),比如警儒,如下的開(kāi)關(guān)機(jī)命令训裆,

// 01 10 01 01 92 07 17
public void controlpower(boolean isOpen){
    if (mCommandCharacteristic == null) return;
    if (mBluetoothLeService == null) return;
    byte[] setDataAfter;
    if(isOpen){
        byte[] setDataBefore = {0x01, 0x10, 0x01, 0x01};
        byte[] trans = inttobyte(integrityCheck(setDataBefore));
        byte[] transV = Arrays.copyOfRange(trans, 2, 4);
        byte[] setData = byteMerger(setDataBefore, reverse(transV));
        byte[] dd = {0x17};
        setDataAfter = byteMerger(setData, dd);
        LogUtils.debug(TAG, "---setDataAfter[4]="+setDataAfter[4]+",setDataAfter[5]="+setDataAfter[5]);
    }else{
        byte[] setDataBefore = {0x01, 0x10, 0x01, 0x00};
        byte[] trans = inttobyte(integrityCheck(setDataBefore));
        byte[] transV = Arrays.copyOfRange(trans, 2, 4);
        byte[] setData = byteMerger(setDataBefore, reverse(transV));
        byte[] dd = {0x17};
        setDataAfter = byteMerger(setData, dd);
        printDataHex(setDataAfter);
    }

    mCommandCharacteristic.setValue(setDataAfter);
    mBluetoothLeService.writeCharacteristic(mCommandCharacteristic);

}
    mCommandCharacteristic.setValue(setDataAfter);
    mBluetoothLeService.writeCharacteristic(mCommandCharacteristic);

這兩行代碼眶根,是核心代碼,但是边琉,我們要進(jìn)行字節(jié)數(shù)組的正確傳遞属百,這里,給大家貼出來(lái)幾個(gè)很有可能用到的方法变姨,

CRC算法Java版:

//crc java
public int integrityCheck(byte[] bytes) {
    int wCrc = 0xffff;
    for (byte srcData : bytes) {
        int data = byteToInt(srcData);
        for(int j = 0; j < 8; j++) {
            if ((((wCrc & 0x8000) >> 8) ^ ((data << j) & 0x80)) != 0) {
                wCrc = (wCrc << 1) ^ 0x1021;
            } else {
                wCrc = wCrc << 1;
            }
        }
    }

    wCrc = (wCrc << 8) | (wCrc >> 8 & 0xff);
    return wCrc & 0xffff;
}
public static int byteToInt(byte b) {
    return b & 0xff;
}

// int to byte
public static byte[] inttobyte(int value) {
    byte b0 = (byte) ((value >> 24) & 0xFF);
    byte b1 = (byte) ((value >> 16) & 0xFF);
    byte b2 = (byte) ((value >> 8) & 0xFF);
    byte b3 = (byte) (value & 0xFF);
    byte[] bytes = { b0, b1, b2, b3 };
    return bytes;
}
//打印字節(jié)數(shù)組
public void printDataHex(byte[] data) {
    if(SENTLOG){
        StringBuilder stringBuilder = new StringBuilder(data.length);
        for(byte byteChar : data)
            stringBuilder.append(String.format("0x%02X ", byteChar));
        String log = stringBuilder.toString();
        LogUtils.debug(TAG, "---發(fā)送到藍(lán)牙的字節(jié)數(shù)組為="+ log);
    }
}

//數(shù)組倒序
public byte[] reverse(byte[] rt){
    for (int i = 0; i < rt.length / 2; i++) {
        byte temp = rt[i];
        rt[i] = rt[rt.length - 1 - i];
        rt[rt.length - 1 - i] = temp;
    }
    return rt;
}

//java 合并兩個(gè)byte數(shù)組
public static byte[] byteMerger(byte[] byte_1, byte[] byte_2){
    byte[] byte_3 = new byte[byte_1.length+byte_2.length];
    System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
    System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
    return byte_3;
}

寫的比較簡(jiǎn)單族扰,但是關(guān)鍵代碼都有了,大家可以參考下定欧!~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末渔呵,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子砍鸠,更是在濱河造成了極大的恐慌扩氢,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爷辱,死亡現(xiàn)場(chǎng)離奇詭異录豺,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)饭弓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門双饥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人示启,你說(shuō)我怎么就攤上這事兢哭∑缃叮” “怎么了丑慎?”我有些...
    開(kāi)封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵简识,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我舍咖,道長(zhǎng),這世上最難降的妖魔是什么锉桑? 我笑而不...
    開(kāi)封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任排霉,我火速辦了婚禮,結(jié)果婚禮上民轴,老公的妹妹穿的比我還像新娘攻柠。我一直安慰自己,他們只是感情好后裸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布瑰钮。 她就那樣靜靜地躺著,像睡著了一般微驶。 火紅的嫁衣襯著肌膚如雪浪谴。 梳的紋絲不亂的頭發(fā)上开睡,一...
    開(kāi)封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天,我揣著相機(jī)與錄音苟耻,去河邊找鬼篇恒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凶杖,可吹牛的內(nèi)容都是我干的胁艰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼智蝠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腾么!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起寻咒,我...
    開(kāi)封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤哮翘,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后毛秘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體饭寺,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年叫挟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艰匙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抹恳,死狀恐怖员凝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奋献,我是刑警寧澤健霹,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站瓶蚂,受9級(jí)特大地震影響糖埋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窃这,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一瞳别、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧杭攻,春花似錦祟敛、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至痪宰,卻和暖如春叼架,著一層夾襖步出監(jiān)牢的瞬間畔裕,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工乖订, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扮饶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓乍构,卻偏偏與公主長(zhǎng)得像甜无,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子哥遮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容