-1

I am trying to manage my temperature service and LED service of my BLE device at the same Android interface. However, whenever I try to turn the LED on, the app dies, and I don't know how to fix this (I am not good at either Java or BLE).

I have all of the UUIDs of the characteristics and services I need. I am trying to create two BluetoothGattCharacteristic

private BluetoothGattCharacteristic characteristic;
private BluetoothGattCharacteristic characteristicCTRL2;

It works fine when I call the temperature characteristic and get the temperature measurement. However, the app dies whenever I try to turn the LED on /call the LED characteristic.

10-30 08:48:33.026 1033-1033/com.example.android.bluetoothlegatt E/AndroidRuntime: FATAL EXCEPTION: main
                                                                               Process: com.example.android.bluetoothlegatt, PID: 1033
                                                                               java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.bluetooth.BluetoothGattCharacteristic.setValue(byte[])' on a null object reference
                                                                                   at com.example.android.bluetoothlegatt.AutoConnectActivity.turnLEDon(AutoConnectActivity.java:71)
                                                                                   at com.example.android.bluetoothlegatt.AutoConnectActivity$6.onClick(AutoConnectActivity.java:264)
                                                                                   at android.view.View.performClick(View.java:4756)
                                                                                   at android.view.View$PerformClick.run(View.java:19754)
                                                                                   at android.os.Handler.handleCallback(Handler.java:739)
                                                                                   at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                                   at android.os.Looper.loop(Looper.java:135)
                                                                                   at android.app.ActivityThread.main(ActivityThread.java:5219)
                                                                                   at java.lang.reflect.Method.invoke(Native Method)
                                                                                   at java.lang.reflect.Method.invoke(Method.java:372)
                                                                                   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
                                                                                   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

The following code is how I transfer all characteristics & services together.

 // Loops through available Characteristics.
        for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
            charas.add(gattCharacteristic);
            if ( gattCharacteristic.getUuid().equals(UUID.fromString(SampleGattAttributes.CTRL_2))){ //CTRL_2 is the UUID of my LED characteristic
                Log.e(TAG, "GattCharacteristics= " + gattCharacteristic.getUuid());
                Intent intent = new Intent(DeviceControlActivity.this, AutoConnectActivity.class);
                intent.putExtra("character_id", "F000AA01-0451-4000-B000-000000000000"); //UUID of my temperature characteristic
                intent.putExtra("service_id", "F000AA00-0451-4000-B000-000000000000");//UUID of my temperature service
                intent.putExtra("address", mDeviceAddress);
                intent.putExtra("ctrl2_character_id", gattCharacteristic.getUuid().toString()); //UUID of my LED characteristics
                intent.putExtra("ctrl2_service_id", gattCharacteristic.getService().getUuid().toString());//UUID of my LED service
                startActivity(intent);
            }  

The part I fail is the function that I try to turn LED on.

public boolean turnLEDon()
    {

    byte[] value = {(byte)1};
    characteristicCTRL2.setValue(value);//This is the line I failed

    boolean status = mBluetoothLeService.mBluetoothGatt.writeCharacteristic(characteristicCTRL2);

    return status;
};

The following is my complete code of trying to manipulate two characteristics at the same interface.

package com.example.android.bluetoothlegatt;



public class AutoConnectActivity extends Activity {
private BluetoothGattCharacteristic characteristic;
private BluetoothGattCharacteristic characteristicCTRL2;
private String address;
private UUID serviceId;
private UUID charId;
private UUID ctrl2ServiceId;
private UUID ctrl2CharId;
private TextView debugText;
private Button startBtn;
private Button stopBtn;
private Button ctrlStartBtn;
private Button ctrlStopBtn;
private Timer timer = new Timer();
private boolean started = false;
private ArrayAdapter<String> adapter;
private String Entry;
private File file;
private boolean check= true;


private BluetoothLeService mBluetoothLeService;



public boolean turnLEDon()
{

    byte[] value = {(byte)1};
    characteristicCTRL2.setValue(value);//This is the line I failed

    boolean status = mBluetoothLeService.mBluetoothGatt.writeCharacteristic(characteristicCTRL2);

    return status;
};
public boolean turnLEDoff()
{

    byte[] value = {(byte)0};
    characteristicCTRL2.setValue(value);

    boolean status = mBluetoothLeService.mBluetoothGatt.writeCharacteristic(characteristicCTRL2);

    return !status;
};

private TimerTask timerTask = new TimerTask() {
    @Override
    public void run() {

        mBluetoothLeService.connect(address);
    }
};


// Code to manage Service lifecycle.
private final ServiceConnection mServiceConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName componentName, IBinder service) {
        mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
        if (!mBluetoothLeService.initialize()) {
            Log.e(TAG, "Unable to initialize Bluetooth");
            finish();
        }
        mBluetoothLeService.connect(address);
        List<BluetoothGattService> list = mBluetoothLeService.getSupportedGattServices();
        for(BluetoothGattService s : list){
            if(s.getUuid().compareTo(serviceId) == 0){
                if (check==true) {
                    characteristic = s.getCharacteristic(charId);
                    mBluetoothLeService.disconnect();
                }


                return;
            }
            if(s.getUuid().compareTo(ctrl2ServiceId) == 0){

                if(check==false) {
                    characteristicCTRL2 = s.getCharacteristic(ctrl2CharId);
                }

                return;
            }
        }
        mBluetoothLeService.disconnect();
        Log.e(TAG, "not find device");
        debugText.setText("not find device");
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        mBluetoothLeService = null;
    }
};

private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        debugText.setText(action);
        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
            // if temperature measurement
            if (check==true){
            mBluetoothLeService.readCharacteristic(characteristic);}
            // if control LED
            if(check==false){
            mBluetoothLeService.readCharacteristic(characteristicCTRL2);
                turnLEDon();

            }

        } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
        } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
        } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action))

        {
            if (check==false) {
            String CTRL_Status = intent.getStringExtra(BluetoothLeService.EXTRA_DATA);
            adapter.add("CTRL Status: " + CTRL_Status + "     " + new Date(System.currentTimeMillis()));

            turnLEDoff();
                mBluetoothLeService.disconnect();
            }
        if(check==true) {
            String temperature = intent.getStringExtra(BluetoothLeService.EXTRA_DATA);
            adapter.add("Temperature: " + temperature + "°C     " + new Date(System.currentTimeMillis()));

                Entry = address + "," + new Date(System.currentTimeMillis()).toString() + "," + temperature.toString() + "\n";

                try {
                    FileOutputStream out = new FileOutputStream(file, true);
                    out.write(Entry.getBytes());
                    out.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                mBluetoothLeService.disconnect();
            }
        }
    }
};

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.autoconnect);
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

    Intent intent = getIntent();
    address = intent.getStringExtra("address");
    serviceId = UUID.fromString(intent.getStringExtra("service_id"));
    charId = UUID.fromString(intent.getStringExtra("character_id"));
    ctrl2ServiceId = UUID.fromString(intent.getStringExtra("ctrl2_service_id"));
    Log.e(TAG, " CTRL2 SERVICE: " + ctrl2ServiceId);
    ctrl2CharId = UUID.fromString(intent.getStringExtra("ctrl2_character_id"));
    Log.e(TAG, " CTRL2 CHARAC: " + ctrl2CharId);
    ((TextView)findViewById(R.id.address_txt)).setText(address);
    ((TextView)findViewById(R.id.service_txt)).setText(serviceId.toString());
    ((TextView)findViewById(R.id.char_txt)).setText(charId.toString());
    debugText = (TextView)findViewById(R.id.debug_txt);
    startBtn = (Button)findViewById(R.id.start_btn);
    stopBtn = (Button)findViewById(R.id.stop_btn);
    ctrlStartBtn = (Button)findViewById(R.id.ctrl_start_btn);
    ctrlStopBtn = (Button)findViewById(R.id.ctrl_stop_btn);
    ListView listView = (ListView)findViewById(R.id.result_list);
    adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
    listView.setAdapter(adapter);

    startBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(started) return;
            started = true;
            check=true;

            //External Storage
            String state;
            state = Environment.getExternalStorageState();
            if (Environment.MEDIA_MOUNTED.equals(state)) {
                File root = Environment.getExternalStorageDirectory();
                File Dir = new File(root.getAbsolutePath() + "/MeasurementDataFile");
                if (!Dir.exists()) {
                    Dir.mkdir();
                }
                file = new File(Dir, "Temperature.csv");

            }
            if (check==true){
            timer.schedule(timerTask, 0, 1000 * 5); }
        }
    });

    stopBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if(!started) return;
            started = false;
            check=true;

            timer.cancel();
        }
    });

    ctrlStartBtn.setOnClickListener(new View.OnClickListener()
    {

        @Override
        public void onClick(View v)
        {
            mBluetoothLeService.connect(address);
            check=false;
            if(started) return;
            started = true;



            Toast.makeText (getBaseContext(), "Turn CTRL 1 ON", Toast.LENGTH_SHORT).show();

            Log.e(TAG, " On?: " + turnLEDon());

        }
    });

    ctrlStopBtn.setOnClickListener(new View.OnClickListener() {
        //mBluetoothLeService.connect(address);
        @Override

        public void onClick(View v) {
            if(!started) return;
            started = false;
            check=false;

            Log.e(TAG, " Off?: " + turnLEDoff());
            Toast.makeText (getBaseContext(), "Turn CTRL 1 OFF", Toast.LENGTH_SHORT).show();

        }
    });

    Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
    bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
}

@Override
protected void onResume() {
    super.onResume();
    registerReceiver(mGattUpdateReceiver, DeviceControlActivity.makeGattUpdateIntentFilter());
    if (mBluetoothLeService != null) {
        final boolean result = mBluetoothLeService.connect(address);
        Log.d(TAG, "Connect request result=" + result);
    }
}

@Override
protected void onPause() {
    super.onPause();
    unregisterReceiver(mGattUpdateReceiver);
}

}

Zoe
  • 27,060
  • 21
  • 118
  • 148
Natalie
  • 63
  • 6

1 Answers1

0
  1. All BLE calls are asynchronous. This is really pain-in-neck, but we have what we have. So you cannot just write:

    { boolean status = writeCharacteristic(characteristic); return status; }

and hope all will be ok. In this case, you obtain a result of the writeCharacteristic instruction only, but not the real write operation result! For the last, you need to register the part of your code as BluetoothGattCallback descendant and read OnCharacteristicWrite event, where you can really know the result of your write operation.

  1. All BLE read-write operations should be done in a strict one-by-one sequence and from main app thread only.

  2. Different devices have different capabilities and productivity, so you can obtain different results - IMHO, this is less strange, than Google's decision to realize BLE stack in this manner :)

Pretty BLE guide for Android can be found here: https://droidcon.de/sites/global.droidcon.cod.newthinking.net/files/media/documents/practical_bluetooth_le_on_android_0.pdf

Edit

The link above is broken but the other one can be found here: https://speakerdeck.com/erikhellman/practical-bluetooth-low-energy-on-android

Miamy
  • 2,162
  • 3
  • 15
  • 32
  • Not sure how this relates to the question "why do I get a null pointer exception?" but what you write is otherwise mostly correct except that you need to execute gatt operations on the main thread (there is no such requirement). – Emil Oct 31 '17 at 10:22
  • @Emil, yes, it's not requirement, it's just recommendation, which can help to avoid some ugly issues, like error 133 ( e.g. https://stackoverflow.com/questions/38136103/android-bluetooth-error-133?noredirect=1&lq=1 ) – Miamy Oct 31 '17 at 13:14
  • I think I am still not sure what to do with my code. My turnLEDon and turnLEDoff functions work find when I only call one LED characteristic once, but not I wanna use one button to trigger both my LED characteristics and temperature characteristics. My code is based on the sample code BluetoothLeGatt (imported from Android Studio), so I basically have everything covered in the PDF file. Could you please help clarify what I should do a little bit more? – Natalie Nov 06 '17 at 13:51
  • The BLE guide link is broken. – drewster Aug 25 '20 at 22:16