Bluetooth LE Gatt connection Android 4.4 to read real-time RSSI - java

I am trying to write a small android app (4.4) which searches for several Bluetooth LE devices. Once it has found each device it needs to connect to it and then continually read the RSSI of each device as quickly as it can. I have been trying to get this to work with 6 devices. My current code is as follows:
public class BluetoothGatt extends Activity {
private BluetoothAdapter mBluetoothAdapter;
private static final int REQUEST_ENABLE_BT = 1;
int count = 0;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
System.out.println("Adapter: " + mBluetoothAdapter);
BTScanStart();
}
// Start the Bluetooth Scan
private void BTScanStart() {
if (mBluetoothAdapter == null) {
System.out.println("Bluetooth NOT supported. Aborting.");
return;
} else {
if (mBluetoothAdapter.isEnabled()) {
System.out.println("Bluetooth is enabled...");
// Starting the device discovery
mBluetoothAdapter.startLeScan(mLeScanCallback);
}
}
}
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) {
count++;
System.out.println("Found " + count + ":" + device + " " + rssi + "db");
device.connectGatt(null, false, mGattCallback);
if (count > 5) mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
};
// Gatt Callback
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
System.out.println(gatt.getDevice() + ": Connected.. ");
gatt.readRemoteRssi();
}
if (newState == BluetoothProfile.STATE_DISCONNECTED) {
System.out.println(gatt.getDevice() + ": Disconnected.. ");
}
}
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
System.out.println(gatt.getDevice() + " RSSI:" + rssi + "db ");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
gatt.readRemoteRssi();
}
};
}
I am having the following problems:
1) It connects to the devices successfully, but they all disconnect after around 5 seconds with a 'btm_sec_disconnected - Clearing Pending flag' error. Is there a was to keep them connected?
2) The code works fine for a single device, however when using more than one device only one device prints RSSI updates regularly, others update randomly and some don't update at all.
3) I am not sure what context I should supply when calling device.connectGatt .
Thank you in advance for your thoughts!

For the RSSI issue I will second Sam's answer (https://stackoverflow.com/a/20676785/4248895). It seems that for your use case continuously scanning should be sufficient. You don't want to add the overhead of connection if you can avoid it.
I will answer your other question in that if you do need to connect to these devices for some reason, your stability issues may be related to the fact that you're connecting and scanning simultaneously. Generally speaking, you should not perform any two gatt operations (scanning, connecting, reading, writing, etc.) at the same time.

How about just use startLeScan and get rssi?
If your android device filter the ble devices (like nexus 7), you could stopLeScan/ startLeScan repeatedly.
If not(like samsung s4 with android 4.3), just let it scan, onLeScan(...) will give every devices' rssi continuously.

Related

How can I see the services and the characteristics of the ble module I'm connected to?

I'm making an Android app that is supposed to connect to a BLE-module and should be able to read it's services and characteristics. My app is based on the code of Accent Systems' "iBKS Hello World" application.
I am not using iBeacons, but a different kind of BLE-module. But when I connect to it, the app is not showing a list of services and characteristics as the code indicates should happen.
Can anyone help me fix this problem? Please let me know!
My code:
public class ScanActivity extends AppCompatActivity {
//DEFINE VARS
String TAG = "ScanActivity";
BluetoothAdapter mBluetoothAdapter;
BluetoothGatt mBluetoothGatt;
BluetoothLeScanner scanner;
ScanSettings scanSettings;
private List<String> scannedDeivcesList;
private ArrayAdapter<String> adapter;
//DEFINE LAYOUT
ListView devicesList;
//THIS METHOD RUNS ON APP LAUNCH
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scan);
//Define listview in layout
devicesList = (ListView) findViewById(R.id.devicesList);
//Setup list on device click listener
setupListClickListener();
//Initialize de devices list
scannedDeivcesList = new ArrayList<>();
//Initialize the list adapter for the listview with params: Context / Layout file / TextView ID in layout file / Devices list
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1, scannedDeivcesList);
//Set the adapter to the listview
devicesList.setAdapter(adapter);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
SpannableString s = new SpannableString("Scan for modules");
s.setSpan(new com.accent_systems.ibkshelloworld.TypefaceSpan(this, "Khand-Bold.ttf"), 0, s.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
s.setSpan(new ForegroundColorSpan(Color.parseColor("#3a3c3e")), 0, s.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
setTitle(s);
getSupportActionBar().setDisplayUseLogoEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
//init Bluetooth adapter
initBT();
//Start scan of bluetooth devices
startLeScan(true);
}
#Override
protected void onStop() {
super.onStop();
startLeScan(false);
}
private void initBT(){
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
//Create the scan settings
ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
//Set scan latency mode. Lower latency, faster device detection/more battery and resources consumption
scanSettingsBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
//Wrap settings together and save on a settings var (declared globally).
scanSettings = scanSettingsBuilder.build();
//Get the BLE scanner from the BT adapter (var declared globally)
scanner = mBluetoothAdapter.getBluetoothLeScanner();
}
private void startLeScan(boolean endis) {
if (endis) {
//********************
//START THE BLE SCAN
//********************
//Scanning parameters FILTER / SETTINGS / RESULT CALLBACK. Filter are used to define a particular
//device to scan for. The Callback is defined above as a method.
scanner.startScan(null, scanSettings, mScanCallback);
}else{
//Stop scan
scanner.stopScan(mScanCallback);
}
}
private ScanCallback mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
//Here all the detected BLE devices will be received . "result" contains the device
//address and name as a BLEPeripheral, the advertising content as a ScanRecord, the Rx RSSI
//and the timestamp when received. Type result.get... to see all the available methods you can call.
//Convert advertising bytes to string for a easier parsing. GetBytes may return a NullPointerException. Treat it right(try/catch).
String advertisingString = byteArrayToHex(result.getScanRecord().getBytes());
//Print the advertising String in the LOG with other device info (ADDRESS - RSSI - ADVERTISING - NAME)
Log.i(TAG, result.getDevice().getAddress()+" - RSSI: "+result.getRssi()+"\t - "+advertisingString+" - "+result.getDevice().getName());
//Check if scanned device is already in the list by mac address
boolean contains = false;
for(int i=0; i<scannedDeivcesList.size(); i++){
if(scannedDeivcesList.get(i).contains(result.getDevice().getAddress())){
//Device already added
contains = true;
//Replace the device with updated values in that position
scannedDeivcesList.set(i, result.getRssi()+" "+result.getDevice().getName()+ "\n ("+result.getDevice().getAddress()+")");
break;
}
}
if(!contains){
//Scanned device not found in the list. NEW => add to list
scannedDeivcesList.add(result.getRssi()+" "+result.getDevice().getName()+ "\n ("+result.getDevice().getAddress()+")");
}
//After modify the list, notify the adapter that changes have been made so it updates the UI.
//UI changes must be done in the main thread
runOnUiThread(new Runnable() {
#Override
public void run() {
adapter.notifyDataSetChanged();
}
});
}
};
//Method to convert a byte array to a HEX. string.
private String byteArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for(byte b: a)
sb.append(String.format("%02x", b & 0xff));
return sb.toString();
}
void setupListClickListener(){
devicesList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//Stop the scan
Log.i(TAG, "SCAN STOPPED");
scanner.stopScan(mScanCallback);
//Get the string from the item clicked
String fullString = scannedDeivcesList.get(position);
//Get only the address from the previous string. Substring from '(' to ')'
String address = fullString.substring(fullString.indexOf("(")+1, fullString.indexOf(")"));
//Get BLE device with address
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
//******************************
//START CONNECTION WITH DEVICE AND DECLARE GATT
//******************************
Log.i(TAG,"*************************************************");
Log.i(TAG, "CONNECTION STARTED TO DEVICE "+address);
Log.i(TAG,"*************************************************");
//ConnectGatt parameters are CONTEXT / AUTOCONNECT to connect the next time it is scanned / GATT CALLBACK to receive GATT notifications and data
// Note: On Samsung devices, the connection must be done on main thread
mBluetoothGatt = device.connectGatt(ScanActivity.this, false, mGattCallback);
/*
There is also another simplest way to connect to a device. If you already stored
the device in a list (List<BluetoothDevice>) you can retrieve it directly and
connect to it:
mBluetoothGatt = mList.get(position).connectGatt(MainActivity.this, false, mGattCallback);
*/
}
});
}
//Connection callback
BluetoothGattCallback mGattCallback =
new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
//Device connected, start discovering services
Log.i(TAG, "DEVICE CONNECTED. DISCOVERING SERVICES...");
mBluetoothGatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
//Device disconnected
Log.i(TAG, "DEVICE DISCONNECTED");
}
}
// On discover services method
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//Services discovered successfully. Start parsing services and characteristics
Log.i(TAG, "SERVICES DISCOVERED. PARSING...");
displayGattServices(gatt.getServices());
} else {
//Failed to discover services
Log.i(TAG, "FAILED TO DISCOVER SERVICES");
}
}
//When reading a characteristic, here you receive the task result and the value
#Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//READ WAS SUCCESSFUL
Log.i(TAG, "ON CHARACTERISTIC READ SUCCESSFUL");
//Read characteristic value like:
//characteristic.getValue();
//Which it returns a byte array. Convert it to HEX. string.
} else {
Log.i(TAG, "ERROR READING CHARACTERISTIC");
}
}
//When writing, here you can check whether the task was completed successfully or not
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "ON CHARACTERISTIC WRITE SUCCESSFUL");
} else {
Log.i(TAG, "ERROR WRITING CHARACTERISTIC");
}
}
//In this method you can read the new values from a received notification
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.i(TAG, "NEW NOTIFICATION RECEIVED");
//New notification received. Check the characteristic it comes from and parse to string
/*if(characteristic.getUuid().toString().contains("0000fff3")){
characteristic.getValue();
}*/
}
//RSSI values from the connection with the remote device are received here
#Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
Log.i(TAG, "NEW RSSI VALUE RECEIVED");
//Read remote RSSI like: mBluetoothGatt.readRemoteRssi();
//Here you get the gatt table where the rssi comes from, the rssi value and the
//status of the task.
}
};
//Method which parses all services and characteristics from the GATT table.
private void displayGattServices(List<BluetoothGattService> gattServices) {
//Check if there is any gatt services. If not, return.
if (gattServices == null) return;
// Loop through available GATT Services.
for (BluetoothGattService gattService : gattServices) {
Log.i(TAG, "SERVICE FOUND: "+gattService.getUuid().toString());
//Loop through available characteristics for each service
for (BluetoothGattCharacteristic gattCharacteristic : gattService.getCharacteristics()) {
Log.i(TAG, " CHAR. FOUND: "+gattCharacteristic.getUuid().toString());
}
}
//****************************************
// CONNECTION PROCESS FINISHED!
//****************************************
Log.i(TAG, "*************************************");
Log.i(TAG, "CONNECTION COMPLETED SUCCESFULLY");
Log.i(TAG, "*************************************");
}
}
You may find full description here. And here short example. So you bring list of services after BLE connection and services request. Later you may request characteristics for every service. Main idea when you bring services one by one you have it UUID and reference. If you have know some service that you like to use you should know it before bring all services. And on collecting them save somewhere reference of service test every by known UUID. Later use this reference for bring characteristics and interconnection with device.

How can i refresh the list of bluetooth devices in java

I am trying to make an android application about localization, i am trying however to make a list of Bluetooth devises found by my smartphone and refresh that lists information every 10 seconds. I want to do that because I am also recording the signal strength (RSSI) and want to record when a beacon is getting out of range when moved or the signal is getting weaker. So refreshing the list will help with that. I have provided some of my code below.
#Override protected void onResume() {
super.onResume();
registerReceiver(devicesFoundReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND));
registerReceiver(devicesFoundReceiver, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED));
registerReceiver(devicesFoundReceiver, new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED));
}
private final BroadcastReceiver devicesFoundReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)){
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
int rssid = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
listAdapter.add(device.getName() + "\n" + device.getAddress() + "\n" + " RSSI: " + rssid + "dBm");
listAdapter.notifyDataSetChanged();
}else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
scanningButton.setText("Scan Bluetooth Devices");
}else if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)){
scanningButton.setText("Scanning in progress ...");
}
}
};
use a listview then call notifyDataSetChanged() on your Adapter object

Android BluetoothDevice notify remote unbond change state

Problem: I am being notified when a bluetooth device is pairing, or becomes paired. However, I do not know how to get notified when a device is being unpaired. How could I get my app to recognize when it has become unpaired with my device?
Example: Let's say that I pair my surface tablet and my android phone through code. My broadcast receiver will register the ACTION_BOND_STATE_CHANGED intent and allow me to process it however I want. Now, if I want to unpair my surface tablet, I do not get notified on my phone that the device has become unpaired. If I check, device.getBondState() for the device after it has been unpaired, it equals BOND_BONDED (which it clearly isnt) I can get it to recognize it is unpaired again after I try to connect to the unpaired device, then the app crashes and then when I turn the app on again, only then does it recognize it as unpaired.
Scenario: I have a BroadcastReceiver registered to listen for intents caused by a BluetoothDevice
getActivity().registerReceiver(broadcastReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND));
getActivity().registerReceiver(broadcastReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
getActivity().registerReceiver(broadcastReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED));
getActivity().registerReceiver(broadcastReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
I process these Actions with this BroadcastReciever:
final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
System.out.println("ACTION: "+ action);
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
list();
// Get the BluetoothDevice object from the intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
System.out.println("FOUND: " + device.getName() + " STATE: " + device.getBondState());
mapInput(device);
// Add the name and the mac address of the object to the array adapter
BTSimpleAdapter.notifyDataSetChanged();
} else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
System.out.println("UPDATE Name " + device.getName() + " Value " + device.getAddress() + " Bond state " + device.getBondState());
for (Map<String, String> entry : data) {
if (entry.get("Name").equals(device.getName()) && entry.get("Value").equals(device.getAddress())) {
if (device.getBondState() == BluetoothDevice.BOND_NONE) {
entry.put("Paired", "Unpaired");
} else if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
entry.put("Paired", "Pairing");
} else {
entry.put("Paired", "Paired");
}
BTSimpleAdapter.notifyDataSetChanged();
}
}
}
}
};

Android returning "null" for Bluetooth RSSI

I am developing a android application using Eclipse to return the RSSI values of a Bluetooth device. I have modified the Android Bluetooth Chat example to fit my needs, but I am having problems returning the RSSI value. After hitting the scan button to discover nearby devices it returns the device name, device address, and is also suppose to return the RSSI value but instead it says null for the RSSI.
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress() + getRssi());
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// Get the Bluetooth RSSI
short Rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI,Short.MIN_VALUE);
// If it's already paired, skip it, because it's been listed already
// Added getRssi()
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress() + getRssi());
}
// When discovery is finished, change the Activity title
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
setProgressBarIndeterminateVisibility(false);
setTitle(R.string.select_device);
if (mNewDevicesArrayAdapter.getCount() == 0) {
String noDevices = getResources().getText(R.string.none_found).toString();
mNewDevicesArrayAdapter.add(noDevices);
}
}
}
};
I haven't tried it myself, but maybe this SO answer will help:
https://stackoverflow.com/a/2361630/831918
Supposedly it is possible using the NDK. Good luck!
This is how I am getting RSSI
String deviceRSSI =
(intent.getExtras()).get(BluetoothDevice.EXTRA_RSSI).toString();
private final BroadcastReceiver ActionFoundReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String deviceRSSI = (intent.getExtras()).get(BluetoothDevice.EXTRA_RSSI).toString();
btArrayAdapter.add(device.getName() + "\n" + device.getAddress() + "\n" + deviceRSSI);
btArrayAdapter.notifyDataSetChanged();
}
if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
}
}
};

android bluetooth discovery: ACTION_FOUND happening after ACTION_DISCOVERY_FINISHED

I am implementing Bluetooth support via implementing a task which searches for devices in the background and provides a list when the search is complete. However, this list occasionally contains a No devices found entry (added only when ACTION_DISCOVERY_FINISHED is received with no devices already in the list) before listing other devices!
private BroadcastReceiver mBlueToothInfoDiscoveryListener = new BroadcastReceiver() {
/**
* This is an overridden function called by the framework whenever there
* is some broadcast message for Bluetooth info discovery listener
*/
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// If it's already paired, skip it, because it's been listed
// already
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
mNewBtDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
// When discovery is finished, change the Activity title
setProgressBarIndeterminateVisibility(false);
setTitle("device list");
if (mNewBtDevicesArrayAdapter.getCount() == 0) {
String noDevices = "No devices found";
mNewBtDevicesArrayAdapter.add(noDevices);
}
}
}
};
I don't expect ACTION_DISCOVERY_FINISHED to ever come before a ACTION_FOUND event, so why would the No devices found string be added to the list before a device is located?
Simply because the discovery task has a timeout to prevent endless searched. So, in case there is no available device around, you'll get your "No Devices found"...

Categories

Resources