读取并通知BLE android

Posted

技术标签:

【中文标题】读取并通知BLE android【英文标题】:Read and notify BLE android 【发布时间】:2018-10-01 21:09:07 【问题描述】:

我编写了一个代码,当它发生变化时,我可以从心率传感器读取通知值。

但我还需要读取设备的电池电量,这是模拟设备的另一项服务,但每当我尝试读取电池值时,第一部分甚至不发送来自心率的通知值.

代码如下:

public class MainActivity extends AppCompatActivity 

BluetoothManager btManager;
BluetoothAdapter btAdapter;
BluetoothLeScanner btScanner;
Button startScanningButton;
Button stopScanningButton;
TextView peripheralTextView;
private final static int REQUEST_ENABLE_BT = 1;
private static final int PERMISSION_REQUEST_COARSE_LOCATION = 1;

Boolean btScanning = false;
int deviceIndex = 0;
ArrayList<BluetoothDevice> devicesDiscovered = new ArrayList<BluetoothDevice>();
EditText deviceIndexInput;
Button connectToDevice;
Button disconnectDevice;
BluetoothGatt bluetoothGatt;

UUID HEART_RATE_SERVICE_UUID = convertFromInteger(0x180D);
UUID HEART_RATE_MEASUREMENT_CHAR_UUID = convertFromInteger(0x2A37);
UUID HEART_RATE_CONTROL_POINT_CHAR_UUID = convertFromInteger(0x2A39);
UUID CLIENT_CHARACTERISTIC_CONFIG_UUID = convertFromInteger(0x2902);
UUID BATTERY_LEVEL = convertFromInteger(0x2A19);
UUID BATTERY_SERVICE = convertFromInteger(0x180F);

public final static String ACTION_GATT_CONNECTED =
"com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.example.bluetooth.le.EXTRA_DATA";

public Map<String, String> uuids = new HashMap<String, String>();

// Stops scanning after 5 seconds.
private Handler mHandler = new Handler();
private static final long SCAN_PERIOD = 1000;

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

    peripheralTextView = (TextView) findViewById(R.id.PeripheralTextView);
    peripheralTextView.setMovementMethod(new ScrollingMovementMethod());
    deviceIndexInput = (EditText) findViewById(R.id.InputIndex);
    deviceIndexInput.setText("0");

    connectToDevice = (Button) findViewById(R.id.ConnectButton);
    connectToDevice.setOnClickListener(new View.OnClickListener() 
        public void onClick(View v) 
            connectToDeviceSelected();
        
    );

    disconnectDevice = (Button) findViewById(R.id.DisconnectButton);
    disconnectDevice.setVisibility(View.INVISIBLE);
    disconnectDevice.setOnClickListener(new View.OnClickListener() 
        public void onClick(View v) 
            disconnectDeviceSelected();
        
    );

    startScanningButton = (Button) findViewById(R.id.StartScanButton);
    startScanningButton.setOnClickListener(new View.OnClickListener() 
        public void onClick(View v) 
            startScanning();
        
    );

    stopScanningButton = (Button) findViewById(R.id.StopScanButton);
    stopScanningButton.setOnClickListener(new View.OnClickListener() 
        public void onClick(View v) 
            stopScanning();
        
    );
    stopScanningButton.setVisibility(View.INVISIBLE);

    btManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    btAdapter = btManager.getAdapter();
    btScanner = btAdapter.getBluetoothLeScanner();

    if (btAdapter != null && !btAdapter.isEnabled()) 
        Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
    

    // Make sure we have access coarse location enabled, if not, prompt the user to enable it
/*        if (this.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) 
        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("This app needs location access");
        builder.setMessage("Please grant location access so this app can detect peripherals.");
        builder.setPositiveButton(android.R.string.ok, null);
        builder.setOnDismissListener(new DialogInterface.OnDismissListener() 
            @Override
            public void onDismiss(DialogInterface dialog) 
                requestPermissions(new String[]Manifest.permission.ACCESS_COARSE_LOCATION, PERMISSION_REQUEST_COARSE_LOCATION);
            
        );
        builder.show();
    */



    //client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build();


// Device scan callback.
private ScanCallback leScanCallback = new ScanCallback() 
    @Override
    public void onScanResult(int callbackType, ScanResult result) 
        peripheralTextView.append("Index: " + deviceIndex + ", Device Name: " + result.getDevice().getName() + " rssi: " + result.getRssi() + ", MAC: " + result.getDevice().getAddress() + "\n");
        devicesDiscovered.add(result.getDevice());
        deviceIndex++;
        // auto scroll for text view
        final int scrollAmount = peripheralTextView.getLayout().getLineTop(peripheralTextView.getLineCount()) - peripheralTextView.getHeight();
        // if there is no need to scroll, scrollAmount will be <=0
        if (scrollAmount > 0) 
            peripheralTextView.scrollTo(0, scrollAmount);
        
    
;

// Device connect call back
private final BluetoothGattCallback btleGattCallback = new BluetoothGattCallback() 

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) 
        // this will get called anytime you perform a read or write characteristic operation

        final byte[] teste = characteristic.getValue();

        final String batida = teste.toString();

        final String result = "result";

        int format = BluetoothGattCharacteristic.FORMAT_UINT8;

        final int heartRate = characteristic.getIntValue(format, 1);

        String TAG = "d";

        Log.d(TAG, String.format("Received heart rate: %d", heartRate));

        Log.v(result , batida);

        MainActivity.this.runOnUiThread(new Runnable() 
            public void run() 
                peripheralTextView.append("value of sensor (BPM) "+ heartRate + "\n");
            
        );
    

    @Override
    public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) 
        // this will get called when a device connects or disconnects
        System.out.println(newState);
        switch (newState) 
            case 0:
            MainActivity.this.runOnUiThread(new Runnable() 
                public void run() 
                    peripheralTextView.append("device disconnected\n");
                    connectToDevice.setVisibility(View.VISIBLE);
                    disconnectDevice.setVisibility(View.INVISIBLE);
                
            );
            break;
            case 2:
            MainActivity.this.runOnUiThread(new Runnable() 
                public void run() 
                    peripheralTextView.append("device connected\n");
                    connectToDevice.setVisibility(View.INVISIBLE);
                    disconnectDevice.setVisibility(View.VISIBLE);
                
            );

                // discover services and characteristics for this device
            bluetoothGatt.discoverServices();

            break;
            default:
            MainActivity.this.runOnUiThread(new Runnable() 
                public void run() 
                    peripheralTextView.append("we encounterned an unknown state, uh oh\n");
                
            );
            break;
        
    

    @Override
    public void onServicesDiscovered(final BluetoothGatt gatt, final int status) 
        // this will get called after the client initiates a            BluetoothGatt.discoverServices() call
        MainActivity.this.runOnUiThread(new Runnable() 
            public void run() 
                peripheralTextView.append("device services have been discovered\n");
            
        );
        displayGattServices(bluetoothGatt.getServices());

        BluetoothGattCharacteristic characteristic = gatt.getService(HEART_RATE_SERVICE_UUID).getCharacteristic(HEART_RATE_MEASUREMENT_CHAR_UUID);

        //BluetoothGattCharacteristic battery = gatt.getService(BATTERY_SERVICE).getCharacteristic(BATTERY_LEVEL);

        //gatt.readCharacteristic(battery);

        gatt.setCharacteristicNotification(characteristic, true);

        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID);

        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);

        gatt.writeDescriptor(descriptor);
    

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) 
        BluetoothGattCharacteristic characteristic = gatt.getService(HEART_RATE_SERVICE_UUID).getCharacteristic(HEART_RATE_CONTROL_POINT_CHAR_UUID);

        characteristic.setValue(new byte[]1,1);
        gatt.writeCharacteristic(characteristic);
    

    @Override
    // Result of a characteristic read operation
    public void onCharacteristicRead(BluetoothGatt gatt,
     BluetoothGattCharacteristic characteristic,
     int status) 
        if (status == BluetoothGatt.GATT_SUCCESS) 
            broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
        
    
;

private void broadcastUpdate(final String action,
 final BluetoothGattCharacteristic characteristic) 

    System.out.println(characteristic.getUuid());


@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) 
    switch (requestCode) 
        case PERMISSION_REQUEST_COARSE_LOCATION: 
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) 
                System.out.println("coarse location permission granted");
             else 
                final AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle("Functionality limited");
                builder.setMessage("Since location access has not been granted, this app will not be able to discover beacons when in the background.");
                builder.setPositiveButton(android.R.string.ok, null);
                builder.setOnDismissListener(new DialogInterface.OnDismissListener() 

                    @Override
                    public void onDismiss(DialogInterface dialog) 
                    

                );
                builder.show();
            
            return;
        
    


public void startScanning() 
    System.out.println("start scanning");
    btScanning = true;
    deviceIndex = 0;
    devicesDiscovered.clear();
    peripheralTextView.setText("");
    peripheralTextView.append("Started Scanning\n");
    startScanningButton.setVisibility(View.INVISIBLE);
    stopScanningButton.setVisibility(View.VISIBLE);
    AsyncTask.execute(new Runnable() 
        @Override
        public void run() 
            btScanner.startScan(leScanCallback);
        
    );

    mHandler.postDelayed(new Runnable() 
        @Override
        public void run() 
            stopScanning();
        
    , SCAN_PERIOD);


public void stopScanning() 
    System.out.println("stopping scanning");
    peripheralTextView.append("Stopped Scanning\n");
    btScanning = false;
    startScanningButton.setVisibility(View.VISIBLE);
    stopScanningButton.setVisibility(View.INVISIBLE);
    AsyncTask.execute(new Runnable() 
        @Override
        public void run() 
            btScanner.stopScan(leScanCallback);
        
    );


public void connectToDeviceSelected() 
    peripheralTextView.append("Trying to connect to device at index: " + deviceIndexInput.getText() + "\n");
    int deviceSelected = Integer.parseInt(deviceIndexInput.getText().toString());
    bluetoothGatt = devicesDiscovered.get(deviceSelected).connectGatt(this, false, btleGattCallback);


public void disconnectDeviceSelected() 
    peripheralTextView.append("Disconnecting from device\n");
    bluetoothGatt.disconnect();


private void displayGattServices(List<BluetoothGattService> gattServices) 
    if (gattServices == null) return;

    // Loops through available GATT Services.
    for (BluetoothGattService gattService : gattServices) 

        final String uuid = gattService.getUuid().toString();
        System.out.println("Service discovered: " + uuid);
        MainActivity.this.runOnUiThread(new Runnable() 
            public void run() 
                peripheralTextView.append("Service disovered: "+uuid+"\n");
            
        );
        new ArrayList<HashMap<String, String>>();
        List<BluetoothGattCharacteristic> gattCharacteristics =
        gattService.getCharacteristics();

        // Loops through available Characteristics.
        for (BluetoothGattCharacteristic gattCharacteristic :
            gattCharacteristics) 

            final String charUuid = gattCharacteristic.getUuid().toString();
        System.out.println("Characteristic discovered for service: " + charUuid);
        MainActivity.this.runOnUiThread(new Runnable() 
            public void run() 
                peripheralTextView.append("Characteristic discovered for service: "+charUuid+"\n");
            
        );

    



@Override
public void onStart() 
    super.onStart();


@Override
public void onStop() 
    super.onStop();



public UUID convertFromInteger(int i) 
    final long MSB = 0x0000000000001000L;
    final long LSB = 0x800000805f9b34fbL;
    long value = i & 0xFFFFFFFF;
    return new UUID(MSB | (value << 32), LSB);



如何在不中断 BLE 通知的情况下阅读?

【问题讨论】:

【参考方案1】:

正如您所观察到的,您不能同时启动这两个操作(第一个操作将被覆盖并且永远不会执行):

gatt.readCharacteristic(battery);
gatt.writeDescriptor(descriptor);

相反,您必须执行第一个操作,等待它完成(在您的示例中,等待调用onCharacteristicRead),然后然后开始第二个操作。实现这一点的一种常见方法是使用queue 来存储待处理的操作,然后将项目从队列前面弹出并在其余代码中一次执行一个。

这种需要排队是 Android BLE API 的一个令人讨厌的方面,文档中没有描述。有关更多详细信息,请查看我的 talk 或 list of resources Android BLE。

【讨论】:

我明白了,所以你的意思是我不能“同时”阅读所有内容?因为目标是连接到具有 4 项服务的设备,并不断监测心率、血液中的氧气百分比、温度和电池电量。 最终可能会同时进行所有持续监控,但您的 Android 应用代码中每个通知的设置必须串行进行,而不是并行进行。因此,要设置多个通知,您可以执行以下操作:调用 gatt.writeDescriptor 设置第一个通知 -> 等待调用 onDescriptorWrite -> 调用 gatt.writeDescriptor 设置第二个通知 -> 等待onDescriptorWrite被调用->调用gatt.writeDescriptor设置第三个通知->等待onDescriptorWrite被调用等 同一主题的更多内容:***.com/questions/47097298/…

以上是关于读取并通知BLE android的主要内容,如果未能解决你的问题,请参考以下文章

识别正在通知的 BLE 设备

无法使用 BLE gatt 回调函数连续读取数据

获得蓝牙低功耗 (BLE) 设备通知的步骤是啥?

低功耗蓝牙通知特性

通过 BLE 通知接收数据包

samsung ble api 无法从多个 GATT 特征中获取通知