AndroidStudio低能耗蓝牙协议BLE通信

1、前言

SPP是蓝牙2.0版本时主要采用的通信协议,需要通过设备MAC地址建立socket连接,然后通过inputStream和outputStream进行通信。

BLE是蓝牙4.0版本开始使用的通信协议,是基于Gatt(通用属性配置文件)的通信方式。BLE支持扫描周边设备并建立连接,而SPP需要提前建立蓝牙连接。BLE扫描周边设备并获取想要的设备,建立gatt连接,通过双方设备定义好的服务特性获取BluetoothGattService并进行相关定义;通过发送数据特性从service获取BluetoothGattCharacteristic并进行相关定义;通过接收数据特性从characteristic获取BluetoothGattDescriptor并进行相关定义。之后便可以通过gatt进行通信。

  private static final UUID SERVICE_CHARACTERISTIC_UUID = UUID.fromString("0000FFE0-0000-1000-8000-00805F9b34FB");
  private static final UUID SEND_CHARACTERISTIC_UUID = UUID.fromString("0000FFE1-0000-1000-8000-00805F9b34FB");
  private static final UUID RECEIVE_CHARACTERISTIC_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
2、实现过程

(1)在配置文件AndroidManifest.xml中<manifest></manifest>引入权限:

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

(2)在入口文件MainActivity的入口函数onCreate方法中获取蓝牙适配器mBluetoothManager和扫描控制器mBluetoothLeScanner;判断是否支持BLE协议;判断是否打开蓝牙,若无则请求用户打开,用户处理结果在onActivityResult方法中响应;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = mBluetoothManager.getAdapter();
        mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();

        if (mBluetoothAdapter == null
                //系统是否支持BLE
                && !getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Log.e(TAG, "not support bluetooth");
        } else {
            Log.e(TAG, " support bluetooth");
        }
        if (!mBluetoothAdapter.isEnabled()) {
            // Request user to enable Bluetooth if not enabled
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        } else {
            // Start device scanning and connect to the device
            Log.i(TAG, "开始扫描:");
            requestPermissions();
        }

        mConnectButton = findViewById(R.id.connect_button);
        mConnectButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startScan();
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_ENABLE_BT) {
            if (resultCode == RESULT_OK) {
                // Bluetooth is enabled, start scanning for BLE devices
                requestPermissions();
            } else {
                // User denied the request to enable Bluetooth
                Toast.makeText(this, "Bluetooth is required for this app to work.", Toast.LENGTH_SHORT).show();
            }
        }
    }

(3)若具备BLE通信能力,且已打开蓝牙,则请求授权定位信息,授权结果在onRequestPermissionsResult方法中响应。这里需要注意Android版本不同使用的方法不同,要注意兼容不同版本号:1)Android4.3以下使用ScanLEStart方法,引入的权限为Corse,需要Activity文件中动态引入;2)Android4.3-12版本使用ScanStart方法,引入权限为FINE,需要Activity文件中动态引入,本文即采用此种方法;3)Android12版本使用ScanStart方法,引入权限为Scan,不用在文件中动态引入。权限引入见第(1)小节。

private void requestPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this,
                    Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                        Manifest.permission.ACCESS_FINE_LOCATION)) {
                    Toast.makeText(this, "使用蓝牙需要授权定位信息", Toast.LENGTH_LONG).show();
                }
                //请求权限
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        REQUEST_ACCESS_FINE_LOCATION_PERMISSION);
            }else{
                startScan();
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_ACCESS_FINE_LOCATION_PERMISSION) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //用户授权
                startScan();
            } else {
                Log.i(TAG, "用户未授权!");
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

(4)开始扫描(设置过滤条件,扫描类型,回调函数):

   private void startScan() {
        if (!mScanning) {
            // 设置过滤条件
            List<ScanFilter> bleScanFilters = new ArrayList<>();
            ScanFilter filter = new ScanFilter.Builder().setDeviceAddress(MAC_ADDRESS).build();
            bleScanFilters.add(filter);
            // 设置扫描设置
            ScanSettings scanSettings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_BALANCED).setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES).setMatchMode(ScanSettings.MATCH_MODE_STICKY).build();
            // 开始扫描
            mBluetoothLeScanner.startScan(bleScanFilters,scanSettings, mScanCallback);

            mScanning = true;
            // 扫描 10 秒后停止扫描
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScanning = false;
                    stopScan();
                }
            }, 10000);
        }
    }
    private void stopScan() {
        if (mScanning) {
            // 停止扫描
            mBluetoothLeScanner.stopScan(mScanCallback);
            mScanning = false;
        }
        Log.d(TAG, "Scan stopped.");
    }

(5)一旦扫描到设备,就会调用回调函数中的onScanResult,若扫描出错,则会调用onScanFailed函数。扫描的回调函数如下:

   private ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            // 当扫描到蓝牙设备时调用
            BluetoothDevice device = result.getDevice();
            String deviceName = device.getName();
            String deviceAddress = device.getAddress();
            Log.d(TAG, "Found device: " + deviceName + " (" + deviceAddress + ")");
            if(deviceName != null && deviceAddress.equals(MAC_ADDRESS)){
                mBluetoothDevice = device;
                connectToDevice(device);
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            // 扫描失败时调用
            Log.e(TAG, "Scan failed with error code: " + errorCode);
        }
    };

(6)扫描到设备后自动调用onScanResult方法,在该方法中根据Mac地址或名称等获取想要连接的设备,并调用设备的connectGatt(上下文,当前Activity类,是否自动连接,回调函数mGattCallback)方法连接设备。

    private void connectToDevice(BluetoothDevice device) {
        // 连接到蓝牙设备
        mBluetoothGatt = device.connectGatt(this, true, mGattCallback);
    }

(7)mGattCallback回调中的方法响应时机如下:

1)onConnectionStateChange:一旦监听到连接状态变化则会调用;

2)onServicesDiscovered:onConnectionStateChange方法中连接成功时调用discoverServices方法。该方法中获取Gatt所有服务和特性,根据设备厂家提供的发送数据特性UUID,找到匹配的服务特性characteristic;根据厂家提供的接收数据特性UUID,获取相应descriptor;将Gatt与characteristic和descriptor绑定;

3)onCharacteristicChanged:以下设置为true时,设备返回数据时的响应;

mBluetoothGatt.setCharacteristicNotification(mCharacteristic, true);

4)onDescriptorWrite:3)中设置为true时,写入数据后的响应;

5)onCharacteristicRead:3)中设置为false时,设备返回数据时的响应;

6)onCharacteristicWrite:3)中设置为false时,写入数据后的响应;

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothGatt.STATE_CONNECTED) {
                // 连接成功
                Log.d(TAG, "Connected to GATT server.");
                // 发现设备的服务
                gatt.discoverServices();
            } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                // 连接断开
                Log.d(TAG, "Disconnected from GATT server.");
                connectToDevice(mBluetoothDevice);
            } else if (newState == BluetoothGatt.STATE_CONNECTING) {
                Log.d(TAG, "Connecting to GATT server.");
                // 连接中
            } else if (newState == BluetoothGatt.STATE_DISCONNECTING) {
                Log.d(TAG, "Disconnecting from GATT server.");
                // 断开连接中
            }
            // 打印 status 错误码
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Log.e(TAG, "Connection error: " + status);
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                // 服务发现成功
                // 获取设备支持的所有服务和特性
                List<BluetoothGattService> services = gatt.getServices();
                // 根据特性的 UUID 查找需要发送数据的特性
                for (BluetoothGattService service : services) {
                    //查看服务UUID
//                    Log.i(TAG, "服务UUID:"+service.getUuid());
                    //查看服务所有特性的UUID
//                    List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
//                    for (int i=0;i<characteristics.size();i++){
//                        BluetoothGattCharacteristic characteristic = characteristics.get(i);
//                        UUID uuid = characteristic.getUuid();
//                        Log.i(TAG, "服务第"+i+"个特性UUID是: "+uuid.toString());
//                    }
                    //获取与设备服务UUID匹配的服务特性,其中设备服务UUID与厂家联系
                    mCharacteristic = service.getCharacteristic(SEND_CHARACTERISTIC_UUID);
                    if (mCharacteristic != null){
                        mBluetoothGatt.setCharacteristicNotification(mCharacteristic, true);
                        // getDescriptor是一个异步操作,因此存在执行到writeDescriptor时descriptpr为空的情况,所以设置一个延时操作,否则无法接收设备回复的数据
                        mHandler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                //必须设置描述才能接收设备发来的数据,其中描述UUID与厂家联系
                                BluetoothGattDescriptor descriptor = mCharacteristic.getDescriptor( RECEIVE_CHARACTERISTIC_UUID);
                                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                                mBluetoothGatt.writeDescriptor(descriptor);
                            }
                        }, 10000);

                       // 将十六进制数组转换为字节数组
                        String hex = ERS_MSG;
                        byte[] byteArray = new byte[hex.length()/2];
                        for( int i=0; i<byteArray.length; i++ ) {
                            //grab the hex in pairs
                            String output = hex.substring(2*i, (2*i + 2));
                            //convert hex to decimal
                            int decimal = Integer.parseInt(output, 16);
                            byteArray[i] = (byte) decimal;
                        }
                        // 发送数据
                        sendDataToDevice(byteArray);
                        stopScan();
                        break;
                    }
                }
            } else {
                // 服务发现失败
                Log.e(TAG, "onServicesDiscovered: Failed");
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            // 特性发生改变时调用,设备主动发送了数据,在此处处理接收到的数据
            byte[] receivedData = characteristic.getValue();
            Log.d(TAG, "Received data: " + Arrays.toString(receivedData));
            if(mBluetoothGatt!=null){
                mBluetoothGatt.disconnect();
                mBluetoothGatt.close();
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            // 读取特性成功时调用,你主动向设备发出了读取请求,并设备回复了数据
            if (status == BluetoothGatt.GATT_SUCCESS) {
                // 在此处处理接收到的数据
                byte[] receivedData = characteristic.getValue();
                Log.d(TAG, "Received data: " + Arrays.toString(receivedData));
            } else {
                // 读取特性失败
                Log.e(TAG, "Failed to read characteristic.");
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                // 写入成功,可以读取回复消息
                Log.i(TAG, "写入成功");
                gatt.readCharacteristic(characteristic);
            }else{
                Log.i(TAG, "写入失败: "+status);
            }
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                // Descriptor写入成功,你可以在这里读取回应的数据
                // 读取数据的逻辑,可以使用gatt.readCharacteristic(characteristic)来读取特征值
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    // 写入成功,可以读取回复消息
                    Log.i(TAG, "descriptor写入成功");
                    BluetoothGattService service = gatt.getService(SERVICE_CHARACTERISTIC_UUID);
                    BluetoothGattCharacteristic characteristic = service.getCharacteristic(SEND_CHARACTERISTIC_UUID);

                    gatt.readCharacteristic(characteristic);
                }else{
                    Log.i(TAG, "descriptor写入失败: "+status);
                }
            }
        }
    };

(8)发送数据:

    private void sendDataToDevice(byte[] data) {
        if (mCharacteristic != null && mBluetoothGatt != null) {
            // 这里是将要发送的数据
            byte[] dataToSend = data;

            mCharacteristic.setValue(dataToSend);
            mCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
            Log.i(TAG, "sendDataToDevice: "+mCharacteristic.getValue());
            mBluetoothGatt.writeCharacteristic(mCharacteristic);
        }
    }

(9)连接真机,进行测试,在日志打印窗口能够看到返回的数据。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值