3

I wrote a code in which I am able to read a notify value from a hear rate sensor when it changes.

But I need also to read the battery level of the device, which is another service from the emulated device, but whenever I try to read the battery value, the first part don't even send the notify values from the heart rate.

Here is the code:

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);
}

}

How can I read without interrupting the notify from BLE?

Mike
  • 4,041
  • 6
  • 20
  • 37
igord93
  • 31
  • 1
  • 2

1 Answers1

0

As you've observed, you can't start these two operations simultaneously (the first one will be overwritten and never executes):

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

Instead, you have to do the first operation, wait for it to finish (in your example, by waiting for onCharacteristicRead to be called), and then start the second operation. A common way to implement this is to use a queue to store pending operations, then pop items off the front of the queue and execute them one at a time in the rest of your code.

This required queuing is an annoying aspect of the Android BLE APIs that is not described in the documentation. For more details, check out my talk or list of resources for Android BLE.

stkent
  • 19,772
  • 14
  • 85
  • 111
  • I see, so you mean that I can not read everything "at the same time"? Because the goal is to connect to the device which will have 4 services and constantly monitor for hear rate, oxygen percentage in the blood, temperature and the battery level. – igord93 Oct 02 '18 at 10:41
  • It's possible to have all that constant monitoring happening simultaneously eventually, but the _setup_ of each notification in your Android app code has to happen in serial, not in parallel. So to set up multiple notifications, you'd do something like: call `gatt.writeDescriptor` to set up the first notification -> wait for `onDescriptorWrite` to be called -> call `gatt.writeDescriptor` to set up the second notification -> wait for `onDescriptorWrite` to be called -> call `gatt.writeDescriptor` to set up the third notification -> wait for `onDescriptorWrite` to be called, etc. – stkent Oct 02 '18 at 12:36
  • 1
    More stuff on the same topic: https://stackoverflow.com/questions/47097298/android-ble-bluetoothgatt-writedescriptor-return-sometimes-false/47103533#47103533 – Emil Oct 02 '18 at 17:09