0

I am working on an app that can connect to an adafruit flora BLE device to receive information from it. I want the app to display the list of found devices and when an item in the list view is clicked the connection is made and data can be received. Ultimately I want to take said data over to another activity to graph in realtime (if possible). There are a few things going on that i dont understand and I hope someone can shed light on.

  1. When it is scanning for devices the list view shows them but there are multiples of each.
  2. For some reason the onPause and onResume make the list view glitchy (displays devices and then removes them)
  3. How do I know when there is a connection?
  4. When I have getRemoteDevice in my code I get a runtime error ( Attempt to invoke virtual method 'android.bluetooth.BluetoothDevice android.bluetooth.BluetoothAdapter.getRemoteDevice(java.lang.String)' on a null object reference)
  5. When I try and use filters and settings in the startScan method I get nothing in my list, I have also tried null filters w/ settings and still nothing.

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.content.Intent;
import android.os.Build;
import android.os.ParcelUuid;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

import no.nordicsemi.android.support.v18.scanner.BluetoothLeScannerCompat;
import no.nordicsemi.android.support.v18.scanner.ScanFilter;
import no.nordicsemi.android.support.v18.scanner.ScanResult;
import no.nordicsemi.android.support.v18.scanner.ScanSettings;

public class BluetoothDiscovery extends AppCompatActivity {

    private String TAG = "Bluetooth Device";
    private int REQUEST_ENABLE_BT = 5;

    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothLeScannerCompat scanner;
    private ScanSettings settings;
    private UUID baseUUID = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e"); // service UUID
    private UUID txUUID = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e"); // TX UUID characteristic
    private UUID rxUUID = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e"); // RX UUID characteristic
    private ScanFilter scanFilter;
    private BluetoothDevice device, mdevice;
    private BluetoothGatt mGatt;
    private boolean mScanning = false;
    private ArrayList<deviceShowFormat> foundDevices = new ArrayList<>();
    formattingAdapter BTadapter;

    Button scanButton;
    TextView fancyWords;
    ListView deviceList;

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

        mBluetoothAdapter.getDefaultAdapter();

        scanButton = findViewById(R.id.scanButt);
        scanButton.setText(getString(R.string.notScanning));

        fancyWords = findViewById(R.id.discoverText);
        fancyWords.setText(getString(R.string.nonScanTitle));

        deviceList = findViewById(R.id.deviceList);
        BTadapter = new formattingAdapter(BluetoothDiscovery.this, foundDevices);
        deviceList.setAdapter(BTadapter);


        scanner = BluetoothLeScannerCompat.getScanner();

        settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_BALANCED).setReportDelay(500).build();

        scanFilter = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(baseUUID)).build();

        //scanner.startScan(Arrays.asList(scanFilter), settings, mScanCallback);

        deviceList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @SuppressLint("LongLogTag")
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                deviceShowFormat mBTDevice = foundDevices.get(i);

                BluetoothDevice Device = mBTDevice.get_device();
                String deviceName = mBTDevice.get_device_name();
                String deviceAddress = mBTDevice.get_device_address();

                Log.d(TAG, "Selected device: " + Device.toString());
                Log.d(TAG, "Selected device name: " + deviceName);
                Log.d(TAG, "Selected device address: " + deviceAddress);



                //BluetoothDevice deviceConnect = mBluetoothAdapter.getRemoteDevice(deviceAddress);
                //deviceConnect.createBond();
                mGatt = Device.connectGatt(BluetoothDiscovery.this, false, mGattCallback);
                Toast.makeText(BluetoothDiscovery.this, "Selected device: " + deviceName, Toast.LENGTH_SHORT).show();

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    Device.createBond();
                }

                Log.d(TAG, "" + Device.getBondState());
                if(Device.getBondState() == BluetoothDevice.BOND_BONDED){
                    Toast.makeText(BluetoothDiscovery.this, "Bluetooth device connected successfully", Toast.LENGTH_SHORT).show();
                }

                mGatt.getServices();
                mGatt.getConnectedDevices();

                Log.d("THIS IS THE DEVICES UUID", String.valueOf(Device.getUuids()));
                Log.d("DEVICE SERVICES", String.valueOf(mGatt.getServices()));
            }
        });
    }

    private final no.nordicsemi.android.support.v18.scanner.ScanCallback mScanCallback = new no.nordicsemi.android.support.v18.scanner.ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);

            Log.i("onScanResult", "device detected");

                device = result.getDevice();
                String deviceName = device.getName();
                String deviceAddress = device.getAddress();

                Log.d(TAG, "Scanned device: " + device.toString());
                Log.d(TAG, "Scanned device name: " + deviceName);
                Log.d(TAG, "Scanned device address: " + deviceAddress);

                foundDevices.add(new deviceShowFormat(device, deviceName, deviceAddress));
                BTadapter.notifyDataSetChanged();
        }
    };

    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);

            Log.i("onConnectionStateChange", "State Changed from: " + status + " to " + newState);

            if (newState == BluetoothProfile.STATE_CONNECTED){
                Toast.makeText(BluetoothDiscovery.this, "Attempting service discovery", Toast.LENGTH_SHORT).show();
                Log.i("onConnectionStateChange", "Attempting service discovery: " + gatt.discoverServices());
                gatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED){
                Toast.makeText(BluetoothDiscovery.this, "Connection has been terminated", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status){
            super.onServicesDiscovered(gatt, status);

            Log.i("onServicesDiscovered", "Hey, we found a service");

            if (status != BluetoothGatt.GATT_SUCCESS){
                // Handle error
                Log.d("onServicesDiscovered" , "" + status);
                return;
            }

            BluetoothGattCharacteristic characteristic = gatt.getService(baseUUID).getCharacteristic(rxUUID);

            gatt.setCharacteristicNotification(characteristic, true);

            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(txUUID);
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            gatt.writeDescriptor(descriptor);
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status){

            Log.i("onCharacteristicRead", "Characteristic has been read");

            readCounterCharacteristic(characteristic);
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            if (mGatt.writeCharacteristic(characteristic)){
                Log.d("Characteristic changed", "Possibly looking for a write");
            }

            if (mGatt.readCharacteristic(characteristic)){
                readCounterCharacteristic(characteristic);
            }
        }

        private void readCounterCharacteristic(BluetoothGattCharacteristic characteristic){

            if (mGatt.readCharacteristic(characteristic)){
                byte[] data = characteristic.getValue();

                Log.d("READ DATA", data.toString());
            }

//            if (rxUUID.equals(characteristic.getUuid())){
//                //byte[] data = characteristic.getValue();
//                byte[] data = mGatt.readCharacteristic(characteristic);
//                //int value = Ints.fromByteArray(data);
//                Log.d("READ DATA", data.toString());
//            }
        }
    };

    public void toggleScan(View view){
        mScanning = !mScanning;

        if(mScanning){
            scanner.startScan(mScanCallback); //Arrays.asList(scanFilter) null, settings,
            scanButton.setText(getString(R.string.scanInProgress));
            fancyWords.setText(getString(R.string.ScanTitle));

        } else {
            scanner.stopScan(mScanCallback);
            scanButton.setText(getString(R.string.notScanning));
        }
    }

//    @Override
//    public void onPause(){
//        super.onPause();
//
////        if(mScanning){
////            mScanning = !mScanning;
//            scanner.stopScan(mScanCallback);
////        }
//
//        //Empty Adapter
//        //BTadapter.clear();
//        //BTadapter.notifyDataSetChanged();
//
//        //mdevice = device;
//
//    }
//
//    @Override
//    public void onResume(){
//        super.onResume();
//
//        if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
//            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
//            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
//        }
//
//        //device = mdevice;
//    }


}

The code is the second activity in my app, in the first bluetooth is initialized and what-not. The above code works but I dont receive any data from the device and am not sure its truly connected. from the logs shown in the code I get:

Logcat Logcat2

Resources im using:

https://github.com/NordicSemiconductor/Android-Scanner-Compat-Library

https://learn.adafruit.com/introducing-the-adafruit-bluefruit-le-uart-friend/uart-service

http://nilhcem.com/android-things/bluetooth-low-energy

3
  • You missed to initialize mBluetoothAdapter before trying to use it Commented Jun 29, 2018 at 19:31
  • What do you mean? I setup mBluetoothAdapter and then used mBluetoothAdapter.getDefaultAdapter() in the onCreate method Commented Jul 3, 2018 at 14:34
  • You missed something like mBluetoothAdapter = instance creation, check answer Commented Jul 3, 2018 at 18:27

4 Answers 4

0

You missed to initialize mBluetoothAdapter

As of (at oncreate):

BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = manager.getAdapter();

instead of:

mBluetoothAdapter.getDefaultAdapter();

After that your variable is initialized and ready to use, just check if you need RuntimePermissions for using bluetooth.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, I did this and nothing broke which is good but which question is this answering? I assume its the getRemoteDevice part because everything else is the same. In my debug log i still get that there is null UUID for the device and there are no services. I used the nRF UART app to check and it did have those things. Can you help guide me?
This answer the 4th one, that unabled you to execute the other codes... now you have to check the other parts
0

Much of the stuff in android BLE communication is asynchronous.

So when you call Device.connectGatt(), you need to wait for the onConnectionStateChange callback before doing the next thing. Your attempts to enumerate the services will fail (probably quietly) because your connection state hasn't gone from 0 to 2 at that point. In your log you can see that you do the getServices() call before it has called you back to say the connection state has gone to connected.

Making things trickier, in my experience you should only be doing your BluetoothDevice interaction from the main thread. (Edit: Only sending requests from the main thread is apparently not a requirement)

So each command you do (connecting, reading characteristic values, writing, disconnecting, enumerating services, etc) needs to look like:

  1. Main Thread: Call the (connect|read|write|enumerate) function
  2. Callback thread: Java calls your BluetoothGattCallback, and you should find a way (Handler?) to signal the main thread to do your next thing.
  3. Main thread: process the result from BluetoothGattCallback and trigger the next thing you'll be doing.

Mild aside: This is a lot easier in objective-c and react-native-ble-plx, because they handle the threading for you. If you design a good way to get the results from the callback thread to the main thread from the beginning, you will save yourself a lot of pain.

Example code for getting stuff back to the UI thread:

Handler m_handler;
public void onResume() {
  m_handler = new Handler(); // creates a Handler bound to the UI thread, which lets us fire messages back and forth between threads.
}

// ... other stuff ... 

public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        super.onConnectionStateChange(gatt, status, newState);

        Log.i("onConnectionStateChange", "State Changed from: " + status + " to " + newState);

        if (newState == BluetoothProfile.STATE_CONNECTED){
            // oh boy, we're connected.  Let's try to discover services!
            // m_handler.post() is a good way to make sure the thing you pass runs on the UI thread.
            m_handler.post(new Runnable() {
                @Override
                public void run() {
                  gatt.discoverServices();
                }
            })
        }
    }

10 Comments

So what I understand from this is that Device.connectGatt() should be the last thing in my "onItemClick", that being the case, where would I put the stuff that comes after so that it still runs? Im not too sure how a callback works but the way its described it doesnt go back to where it left off? Also by thread do you mean activity? Ive seen that terminology used but wast sure if thats what it meant. This has been the most in depth response ive had since Ive started this project a few months ago so Thank you.
I'll edit my answer to give some example code of how I did it, which I'm still not very happy with.
Answer edited. This seems like an alright intro to threading: katyscode.wordpress.com/2013/05/17/… . For threading concepts specific to android, try this out: developer.android.com/guide/components/processes-and-threads
A really hacky and gross but quick way to "wait until connection complete" is to just put a Thread.sleep(5000) after your connectGatt() call. The connection thread should have completed by the time the Thread.sleep(5000) has returned, and you might get your service list back. Note that if the connection takes longer than that, then your program will randomly fail.
There are no requirements for BLE calls to be performed from the main thread. Most BLE callbacks are called from a thread in the Binder threadpool, and only one callback at a time is guaranteed to be processed. Therefore you should not, in a callback, have a loop waiting for another callback to be called.
|
0

For anyone looking this up in the future: If you receive a message similar to D/BluetoothGatt: onConnectionUpdated() - Device=xx:xx:xx:xx:xx:xx interval=6 latency=0 timeout=500 status=0 you are connecting to the device but not "enough" to recieve info. For me it turned out i had to click the device to connect to a second time to actually recieve the data. I dont know why yet

Comments

0

in your answer

For anyone looking this up in the future: If you receive a message similar to D/BluetoothGatt: onConnectionUpdated() - Device=xx:xx:xx:xx:xx:xx interval=6 latency=0 timeout=500 status=0 you are connecting to the device but not "enough" to recieve info. For me it turned out i had to click the device to connect to a second time to actually recieve the data. I dont know why yet

I Unfortunately got this problem, you can see in my question BLE onDescriptorWrite is not trigger when writeDescriptor receive true And What do you say is the second click device, your app or other ble device. Thanks a lot.

1 Comment

For me it was having to click the device I was trying to connect to a second time

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.