javaandroidbluetooth-lowenergyandroid-bluetoothbluetooth-gatt

Manage Properties Of Characteristics of BLE Devices


I'm trying to manage BLE devices by building an Application using Android Studio and Java. I have trouble with handling properties of the characteristics identified in the BLE devices.

Here is my code for the Activity that reading the properties related to the characteristic.

Properties.java:

public class Properties extends AppCompatActivity {
    private static final String TAG = "Properties";
    private BluetoothGattCharacteristic characteristic;
    private BluetoothGatt gatt;

    private Button btnRead, btnWrite, btnNotify;
    private boolean isNotifying = false;

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

        btnRead = findViewById(R.id.btnRead);
        btnWrite = findViewById(R.id.btnWrite);
        btnNotify = findViewById(R.id.btnNotify);

        // Retrieve the characteristic UUID from the Intent
        String characteristicUUID = getIntent().getStringExtra("characteristicUUID");

        // Retrieve the BluetoothGatt and find the characteristic by UUID
        gatt = GattManager.getGatt();
        if (gatt == null) {
            Log.e(TAG, "No Gatt instance received from MainActivity to Properties");
        } else {
            Log.d(TAG, "Gatt instance received from MainActivity to Properties");
        }

        characteristic = findCharacteristicByUUID(characteristicUUID);
        if (characteristic == null) {
            Log.e(TAG, "No BluetoothGattCharacteristic received from intent to Properties");
        } else {
            Log.d(TAG, "BluetoothGattCharacteristic received from intent to Properties");

        }

        // check and enable available properties
        if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) != 0) {
            btnRead.setVisibility(View.VISIBLE);
            btnRead.setOnClickListener(view -> readCharacteristic());
        }
        if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) != 0) {
            btnWrite.setVisibility(View.VISIBLE);
            btnWrite.setOnClickListener(view -> writeCharacteristic());
        }
        if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
            btnNotify.setVisibility(View.VISIBLE);
            btnNotify.setOnClickListener(view -> toggleNotify());
        }

    }

    private BluetoothGattCharacteristic findCharacteristicByUUID(String uuid) {
        if (gatt != null) {
            for (BluetoothGattService service : gatt.getServices()) {
                for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
                    if (characteristic.getUuid().toString().equals(uuid)) {
                        return characteristic;
                    }
                }
            }
        }
        return null;
    }

    private void readCharacteristic() {
        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        gatt.readCharacteristic(characteristic);
        onCharacteristicRead(characteristic);

    }

    private void writeCharacteristic() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Write Value");

        final EditText input = new EditText(this);
        builder.setView(input);

        builder.setPositiveButton("OK", (dialog, which) -> {
            String value = input.getText().toString();
            characteristic.setValue(value.getBytes());
            if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                return;
            }
            boolean success = gatt.writeCharacteristic(characteristic);
            if (success) {
                Toast.makeText(this, "Write successful", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "Write failed", Toast.LENGTH_SHORT).show();
            }
        });
        builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());

        builder.show();
    }

    private void toggleNotify() {
        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
        if (!isNotifying) {
            if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                return;
            }
            gatt.setCharacteristicNotification(characteristic, true);
            if (descriptor != null) {
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                gatt.writeDescriptor(descriptor);
            }
            btnNotify.setText("Stop Notify");
            Toast.makeText(this, "Notifications enabled", Toast.LENGTH_SHORT).show();
        } else {
            gatt.setCharacteristicNotification(characteristic, false);
            if (descriptor != null) {
                descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                gatt.writeDescriptor(descriptor);
            }
            btnNotify.setText("Start Notify");
            Toast.makeText(this, "Notifications disabled", Toast.LENGTH_SHORT).show();
        }
        isNotifying = !isNotifying;
    }

    public void onCharacteristicRead(BluetoothGattCharacteristic characteristic) {

        // Display characteristic value in a dialog
        String value = new String(characteristic.getValue());
        new AlertDialog.Builder(this)
                .setTitle("Characteristic Value")
                .setMessage("Value: " + value)
                .setPositiveButton("OK", null)
                .show();
    }
}

I'm calling the BluetoothGattCallBack from the MainActivity.java, and save the Gatt in GattManager.java.

The GattManager.java:

public class GattManager {
    private static BluetoothGatt gatt;

    public static BluetoothGatt getGatt() {
        return gatt;
    }

    public static void setGatt(BluetoothGatt gattInstance) {
        gatt = gattInstance;
    }

}

Please help me to resolve errors and manage the read, write and notify properties present with the characteristics.

I have tried according to the codes given, but I'm having problems on managing the read and write properties.


Solution

  • While I can't see all your code, I think the trouble you are having with reading characteristics is because you are trying to read as a synchronous operation, when reading from BLE is asynchronous.

    You are doing this:

    gatt.readCharacteristic(characteristic);
    ...
    String value = new String(characteristic.getValue());
    

    This isn't going to work because the value you are trying to read is not there yet. After you call BluetoothGatt.readCharacteristic(characteristic), the result will arrive via BluetoothGattCallback.onCharacteristicRead(gatt, characteristic, status). You supply a BluetoothGattCallback when you call BluetoothDevice.connectGatt(). You need to override onCharacteristicRead() to process the value when it arrives. The value could also fail to be read and in that case you can check the status parameter on that method.

    I'm assuming you call connectGatt() somewhere in MainActivity?? It's going to be difficult with your current setup to call BluetoothGatt.readCharacteristic(characteristic) from Properties and have your callback in MainActivity. You might want to have GattManager do a little bit more like manage the callback or store the characteristic value. So when the callback receives the value in onCharacteristicRead(), it saves the value in GattManager and then Properties can read it. You could even add a listener mechanism to GattManager where Properties could register to receive an event when the characteristic value arrives. Or use a LiveData. Lots of ways to make it work!