文章目录

  • 前言
  • 一、低功耗蓝牙BLE是什么?
  • 二、基于低功耗蓝牙的手机APP
  • 1.工程源码
  • 2.蓝牙接收数据函数
  • 3.数据处理函数(可自定义) 


一、低功耗蓝牙BLE是什么?

低功耗蓝牙是一种全新的技术,是当前可以用来设计和使用的功耗最低的无线技术。

经典蓝牙的设计目的在于统一全球各地的计算和通信设备,让手机与笔记本电脑相互连接。不过事实证明,蓝牙最为广泛的应用还是音频传输,比如将音频从手机传到蓝牙耳机。

低功耗蓝牙选择了完全不同的方向:并非只是增加可达的数据传输速率,而是从尽可能降低功耗方面进行优化。这意味着,也许你无法获得很高的传输速率,但是可以将连接保持数小时或数天的时间。

二、基于低功耗蓝牙的手机APP

1.工程源码

关于低功耗蓝牙的一些操作,包括开启、搜索蓝牙等等

package com.kaka.bluetoothble;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.blankj.utilcode.util.ToastUtils;
import com.kaka.bluetoothble.adapter.BleAdapter;
import com.tbruyelle.rxpermissions2.RxPermissions;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import static android.bluetooth.BluetoothDevice.TRANSPORT_LE;

public class MainActivity extends AppCompatActivity {

    private static final String TAG ="ble_tag" ;
    ProgressBar pbSearchBle;
    ImageView ivSerBleStatus;
    TextView tvSerBleStatus;
    TextView tvSerBindStatus;
    ListView bleListView;
    private LinearLayout operaView;
    private Button btnWrite;
    private Button btnRead;
    private EditText etWriteContent;
    private TextView tvResponse;
    private TextView tvR;
    private TextView tvxlv;
    private TextView tvpa;
    private TextView tvgps;
    private List<BluetoothDevice> mDatas;
    private List<Integer> mRssis;
    private BleAdapter mAdapter;
    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothManager mBluetoothManager;
    private boolean isScaning=false;
    private boolean isConnecting=false;
    private BluetoothGatt mBluetoothGatt;

    //服务和特征值
    private UUID write_UUID_service;
    private UUID write_UUID_chara;
    private UUID read_UUID_service;
    private UUID read_UUID_chara;
    private UUID notify_UUID_service;
    private UUID notify_UUID_chara;
    private UUID indicate_UUID_service;
    private UUID indicate_UUID_chara;
    private String hex="7B46363941373237323532443741397D";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_search_device);
        initView();
        initData();
        mBluetoothManager= (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
        mBluetoothAdapter=mBluetoothManager.getAdapter();
        if (mBluetoothAdapter==null||!mBluetoothAdapter.isEnabled()){
            Intent intent=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent,0);
        }

    }

    private void initData() {
        mDatas=new ArrayList<>();
        mRssis=new ArrayList<>();
        mAdapter=new BleAdapter(MainActivity.this,mDatas,mRssis);
        bleListView.setAdapter(mAdapter);
        mAdapter.notifyDataSetChanged();
    }

    private void initView(){
        pbSearchBle=findViewById(R.id.progress_ser_bluetooth);
        ivSerBleStatus=findViewById(R.id.iv_ser_ble_status);
        tvSerBindStatus=findViewById(R.id.tv_ser_bind_status);
        tvSerBleStatus=findViewById(R.id.tv_ser_ble_status);
        bleListView=findViewById(R.id.ble_list_view);
        operaView=findViewById(R.id.opera_view);
        btnWrite=findViewById(R.id.btnWrite);
        btnRead=findViewById(R.id.btnRead);
        etWriteContent=findViewById(R.id.et_write);
        tvResponse=findViewById(R.id.tv_response);
        tvR=findViewById(R.id.tv_R);
        tvxlv=findViewById(R.id.tv_XLV);
        tvpa=findViewById(R.id.tv_Pa);
        btnRead.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                readData();
            }
        });

        btnWrite.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //执行写入操作
                writeData();
            }
        });


        ivSerBleStatus.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isScaning){
                    tvSerBindStatus.setText("停止搜索");
                    stopScanDevice();
                }else{
                    checkPermissions();
                }

            }
        });
        bleListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (isScaning){
                    stopScanDevice();
                }
                if (!isConnecting){
                    isConnecting=true;
                    BluetoothDevice bluetoothDevice= mDatas.get(position);
                    //连接设备
                    tvSerBindStatus.setText("连接中");
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        mBluetoothGatt = bluetoothDevice.connectGatt(MainActivity.this,
                                true, gattCallback, TRANSPORT_LE);
                    } else {
                        mBluetoothGatt = bluetoothDevice.connectGatt(MainActivity.this,
                                true, gattCallback);
                    }
                }

            }
        });


    }

    private void readData() {
        BluetoothGattCharacteristic characteristic=mBluetoothGatt.getService(read_UUID_service)
                .getCharacteristic(read_UUID_chara);
        mBluetoothGatt.readCharacteristic(characteristic);
    }


    /**
     * 开始扫描 10秒后自动停止
     * */
    private void scanDevice(){
        tvSerBindStatus.setText("正在搜索");
        isScaning=true;
        pbSearchBle.setVisibility(View.VISIBLE);
        mBluetoothAdapter.startLeScan(scanCallback);
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //结束扫描
                mBluetoothAdapter.stopLeScan(scanCallback);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        isScaning=false;
                        pbSearchBle.setVisibility(View.GONE);
                        tvSerBindStatus.setText("搜索已结束");
                    }
                });
            }
        },10000);
    }

    /**
     * 停止扫描
     * */
    private void stopScanDevice(){
        isScaning=false;
        pbSearchBle.setVisibility(View.GONE);
        mBluetoothAdapter.stopLeScan(scanCallback);
    }


    BluetoothAdapter.LeScanCallback scanCallback=new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            Log.e(TAG, "run: scanning...");
            if (!mDatas.contains(device)){
                mDatas.add(device);
                mRssis.add(rssi);
                mAdapter.notifyDataSetChanged();
            }

        }
    };

    private BluetoothGattCallback gattCallback=new BluetoothGattCallback() {
        /**
         * 断开或连接 状态发生变化时调用
         * */
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            Log.e(TAG,"onConnectionStateChange()");
            if (status==BluetoothGatt.GATT_SUCCESS){
                //连接成功
                if (newState== BluetoothGatt.STATE_CONNECTED){
                    Log.e(TAG,"连接成功");
                    //发现服务
                    gatt.discoverServices();
                }
            }else{
                //连接失败
                Log.e(TAG,"失败=="+status);
                mBluetoothGatt.close();
                isConnecting=false;
            }
        }
        /**
         * 发现设备(真正建立连接)
         * */
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            //直到这里才是真正建立了可通信的连接
            isConnecting=false;
            Log.e(TAG,"onServicesDiscovered()---建立连接");
            //获取初始化服务和特征值
            initServiceAndChara();
            //订阅通知
            mBluetoothGatt.setCharacteristicNotification(mBluetoothGatt
                    .getService(notify_UUID_service).getCharacteristic(notify_UUID_chara),true);


            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    bleListView.setVisibility(View.GONE);
                    operaView.setVisibility(View.VISIBLE);
                    tvSerBindStatus.setText("已连接");

                }
            });
        }
        /**
         * 读操作的回调
         * */
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            Log.e(TAG,"onCharacteristicRead()");
        }
        /**
         * 写操作的回调
         * */
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);

            Log.e(TAG,"onCharacteristicWrite()  status="+status+",value="+HexUtil.encodeHexStr(characteristic.getValue()));
        }
        /**
         * 接收到硬件返回的数据
         * */
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            Log.e(TAG,"onCharacteristicChanged()"+characteristic.getValue());
            final byte[] data=characteristic.getValue();
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    // byte[] 转 string
                    String res = new String(data);
             //       addText(tvResponse,bytes2hex(data));
             //       addText(tvR,bytes2hex(data));
                    String[] stra=new String[10];
                    stra=getString(res);
                   tvResponse.setText(stra[1].toString()+" ℃");
                    tvR.setText(stra[2].toString()+" RH");
                    tvxlv.setText(stra[3].toString()+" ℃");
                    tvpa.setText(stra[4].toString()+" RH");


                }
            });

        }
    };
    /**
     * 检查权限
     */
    private void checkPermissions() {
        RxPermissions rxPermissions = new RxPermissions(MainActivity.this);
        rxPermissions.request(android.Manifest.permission.ACCESS_FINE_LOCATION)
                .subscribe(new io.reactivex.functions.Consumer<Boolean>() {
                    @Override
                    public void accept(Boolean aBoolean) throws Exception {
                        if (aBoolean) {
                            // 用户已经同意该权限
                            scanDevice();
                        } else {
                            // 用户拒绝了该权限,并且选中『不再询问』
                            ToastUtils.showLong("用户开启权限后才能使用");
                        }
                    }
                });
    }


    private void initServiceAndChara(){
        List<BluetoothGattService> bluetoothGattServices= mBluetoothGatt.getServices();
        for (BluetoothGattService bluetoothGattService:bluetoothGattServices){
            List<BluetoothGattCharacteristic> characteristics=bluetoothGattService.getCharacteristics();
            for (BluetoothGattCharacteristic characteristic:characteristics){
                int charaProp = characteristic.getProperties();
                if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
                    read_UUID_chara=characteristic.getUuid();
                    read_UUID_service=bluetoothGattService.getUuid();
                    Log.e(TAG,"read_chara="+read_UUID_chara+"----read_service="+read_UUID_service);
                }
                if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
                    write_UUID_chara=characteristic.getUuid();
                    write_UUID_service=bluetoothGattService.getUuid();
                    Log.e(TAG,"write_chara="+write_UUID_chara+"----write_service="+write_UUID_service);
                }
                if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) {
                    write_UUID_chara=characteristic.getUuid();
                    write_UUID_service=bluetoothGattService.getUuid();
                    Log.e(TAG,"write_chara="+write_UUID_chara+"----write_service="+write_UUID_service);

                }
                if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                    notify_UUID_chara=characteristic.getUuid();
                    notify_UUID_service=bluetoothGattService.getUuid();
                    Log.e(TAG,"notify_chara="+notify_UUID_chara+"----notify_service="+notify_UUID_service);
                }
                if ((charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
                    indicate_UUID_chara=characteristic.getUuid();
                    indicate_UUID_service=bluetoothGattService.getUuid();
                    Log.e(TAG,"indicate_chara="+indicate_UUID_chara+"----indicate_service="+indicate_UUID_service);

                }
            }
        }
    }

    //将字符串数据进行显示的函数
    private void addText(TextView textView, String content) {

        textView.setText(content+"℃");

    }

    private void writeData(){
        BluetoothGattService service=mBluetoothGatt.getService(write_UUID_service);
        BluetoothGattCharacteristic charaWrite=service.getCharacteristic(write_UUID_chara);
        byte[] data;
        String content=etWriteContent.getText().toString();
        if (!TextUtils.isEmpty(content)){
            data=HexUtil.hexStringToBytes(content);
        }else{
            data=HexUtil.hexStringToBytes(hex);
        }
        if (data.length>20){//数据大于个字节 分批次写入
            Log.e(TAG, "writeData: length="+data.length);
            int num=0;
            if (data.length%20!=0){
                num=data.length/20+1;
            }else{
                num=data.length/20;
            }
            for (int i=0;i<num;i++){
                byte[] tempArr;
                if (i==num-1){
                    tempArr=new byte[data.length-i*20];
                    System.arraycopy(data,i*20,tempArr,0,data.length-i*20);
                }else{
                    tempArr=new byte[20];
                    System.arraycopy(data,i*20,tempArr,0,20);
                }
                charaWrite.setValue(tempArr);
                mBluetoothGatt.writeCharacteristic(charaWrite);
            }
        }else{
            charaWrite.setValue(data);
            mBluetoothGatt.writeCharacteristic(charaWrite);
        }
    }

    private static final String HEX = "0123456789abcdef";
    public static String bytes2hex(byte[] bytes)
    {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes)
        {
            // 取出这个字节的高4位,然后与0x0f与运算,得到一个0-15之间的数据,通过HEX.charAt(0-15)即为16进制数
            sb.append(HEX.charAt((b >> 4) & 0x0f));
            // 取出这个字节的低位,与0x0f与运算,得到一个0-15之间的数据,通过HEX.charAt(0-15)即为16进制数
            sb.append(HEX.charAt(b & 0x0f));
        }
        return sb.toString();
    }

    //下面是一种稍复杂的写法,将单片机传输过来的数据进行分解,最后得到一个字符串数组
    public static String[] getString(String str) {
        //先定义一个集合来存放分解后的字符
        List<String> list = new ArrayList<String>();
        String streee = "";

        for (int i = 0; i <str.length(); i++) {
            streee = str.substring(i, i + 1);
            list.add(streee);
        }
        //定义一个存放最终字符串的数组
        String[] strk=new String[10];

        //定义一个存放最终字符串的StringBuffer
        StringBuffer stra = new StringBuffer();
        StringBuffer strb = new StringBuffer();
        StringBuffer strc = new StringBuffer();
        StringBuffer strd = new StringBuffer();
        StringBuffer stre = new StringBuffer();

        int i=0;
        for (int j = 0; j < list.size(); j++) {
            String a = list.get(j).toString();
            if (a.equals("$")){
                i++;
                j++;
            }
            //如果是'$'就把这个字符加在上面定义的StringBuffer
            if (i==1){
                String b= list.get(j).toString();
                stra.append(b);
            }else{

            }
            //如果是'$'就把这个字符加在上面定义的StringBuffer
            if (i==2){
                String b= list.get(j).toString();
                strb.append(b);
            }else{

            }
            //如果是'$'就把这个字符加在上面定义的StringBuffer
            if (i==3){
                String b= list.get(j).toString();
                strc.append(b);
            }else{

            }
            //如果是'$'就把这个字符加在上面定义的StringBuffer
            if (i==4){
                String b= list.get(j).toString();
                strd.append(b);
            }else{

            }

        }

        strk[1]=stra.toString();
        strk[2]=strb.toString();
        strk[3]=strc.toString();
        strk[4]=strd.toString();

        return strk;

    }



    @Override
    protected void onDestroy() {
        super.onDestroy();
        mBluetoothGatt.disconnect();
    }
}

2.蓝牙接收数据函数

data为蓝牙接收到的数据

/**
 * 接收到硬件返回的数据
 * */
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
    super.onCharacteristicChanged(gatt, characteristic);
    Log.e(TAG,"onCharacteristicChanged()"+characteristic.getValue());
    final byte[] data=characteristic.getValue();
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            // byte[] 转 string
            String res = new String(data);
     //       addText(tvResponse,bytes2hex(data));
     //       addText(tvR,bytes2hex(data));
            String[] stra=new String[10];
            stra=getString(res);
           tvResponse.setText(stra[1].toString()+" ℃");
            tvR.setText(stra[2].toString()+" RH");
            tvxlv.setText(stra[3].toString()+" ℃");
            tvpa.setText(stra[4].toString()+" RH");


        }
    });

}

3.数据处理函数

可以自定义数据处理函数

//下面是一种稍复杂的写法,将单片机传输过来的数据进行分解,最后得到一个字符串数组
    public static String[] getString(String str) {
        //先定义一个集合来存放分解后的字符
        List<String> list = new ArrayList<String>();
        String streee = "";

        for (int i = 0; i <str.length(); i++) {
            streee = str.substring(i, i + 1);
            list.add(streee);
        }
        //定义一个存放最终字符串的数组
        String[] strk=new String[10];

        //定义一个存放最终字符串的StringBuffer
        StringBuffer stra = new StringBuffer();
        StringBuffer strb = new StringBuffer();
        StringBuffer strc = new StringBuffer();
        StringBuffer strd = new StringBuffer();
        StringBuffer stre = new StringBuffer();

        int i=0;
        for (int j = 0; j < list.size(); j++) {
            String a = list.get(j).toString();
            if (a.equals("$")){
                i++;
                j++;
            }
            //如果是'$'就把这个字符加在上面定义的StringBuffer
            if (i==1){
                String b= list.get(j).toString();
                stra.append(b);
            }else{

            }
            //如果是'$'就把这个字符加在上面定义的StringBuffer
            if (i==2){
                String b= list.get(j).toString();
                strb.append(b);
            }else{

            }
            //如果是'$'就把这个字符加在上面定义的StringBuffer
            if (i==3){
                String b= list.get(j).toString();
                strc.append(b);
            }else{

            }
            //如果是'$'就把这个字符加在上面定义的StringBuffer
            if (i==4){
                String b= list.get(j).toString();
                strd.append(b);
            }else{

            }

        }

        strk[1]=stra.toString();
        strk[2]=strb.toString();
        strk[3]=strc.toString();
        strk[4]=strd.toString();

        return strk;

    }

 

总结

工程下载链接:低功耗蓝牙手机APP工程文件

该低功耗蓝牙APP工程源码以及APK文件可通过我的资源进行下载

(1)该工程为基本模板,可以基于本工程进行进一步开发

(2)根据自己的需求进行定制化开发

(3)来学习低功耗蓝牙