Scanning for BLE devices in a foreground service - android

I am creating an app that can detect and exchange information with BLE devices similar to iBeacons. These devices send an advertisement every 1000ms.
Currently, I have a foreground service that runs some logic, which includes scanning for BLE devices periodically (e.g. once every ten seconds, I run the scanner for 2 seconds). Once I push the app to the background, however, the BLE Scanner changes behaviour, scanning very infrequently. From what I can understand, this is because the OS forces the scanner to use the low power scan mode.
The problem lies in the fact that the scan window is shorter than the BLE Device advertising intervals, so I tend to miss packets. Is there a way to ensure the scanning is able to run for at least this interval, perhaps via scan mode (low latency or balanced) or some other trick?
I am using a Samsung Galaxy Tab S2 running Android 7.0.
Edit: Added some sample code (Note: I am using Nativescript, so what you see here is a conversion of the Nativescript code to Java, to the best of my understanding.)
I start the service with
Intent intent = new Intent(this, com.my.service.MyService);
startService(intent);
Service class functions
#Override
public int onStartCommand(Intent intent, int flags, int startId){
Notification notif = new Notification.Builder(this)
.setContentInfo("My App")
.build()
this.startForeground(12312, notif);
runMyScanner();
this.super.onStartCommand(intent, flags, startId);
return Service.START_STICKY;
}
public void onDestroy(){
this.stopForeground(0); // Corresponds to a flag.
}
Scanner
private int mNumScanned = 0;
public void runMyScanner(){
List<ScanFilter> scanFilters = new ArrayList<>();
scanFilters.add(new ScanFilter.Builder().build())
ScanSettings scanSettings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
ScanCallback scanCallback = new ScanCallback(){
#Override
public void onScanResult(){
mNumScanned++;
}
}
mAdapter.getBluetoothLeScanner().startScan(scanFilterList, scanSettings, scanCallback);
}
(Note: After scanning, I print out mNumScanned, which has allowed me to investigate the issue. In addition, I stop the scan some number of seconds after starting, but use the javascript method setTimeout() to do so)

Related

How to create a job with WorkManager when device is plugged in?

I want to create a job that will be triggered when the device is plugged in, just like broadcast receiver for ACTION_POWER_CONNECTED.
Here is the worker class:
public class ChargerWorker extends Worker {
/* Constants */
private static final String TAG = "ChargerWorker";
private static final long TRIGGER_AGE = TimeUnit.MINUTES.toMillis(30); // At least 30 min old.
#NonNull
#Override
public Result doWork() {
Log.e(TAG, "Power connection worker of Indoor/Outdoor lib.");
IndoorOutdoorLogger.v(TAG, "Power connection worker of Indoor/Outdoor lib.");
Context context = getApplicationContext();
if (Conditions.isBatteryChargingOnAC(context)) {
IndoorOutdoorLogger.d(context, TAG, "Power plugged and connected to AC.");
Log.e(TAG, "Power plugged and connected to AC.");
if (WiFiConnection.isWifiConnected(context) && WiFiConnection.isCurrentNetworkIndoorRecognized(context)) {
// In this case an "upgrade" to the confidence level is possible.
// Only run when the last known detection is old enough.
DetectionResult latestResult = DetectionResult.loadFromCache(context);
if (!latestResult.isTimestampValid(TRIGGER_AGE)) {
IndoorOutdoorLogger.d(context, TAG, "AC power while connected to a recognized WiFi network, and last detection is old enough, starting detection.");
IndoorOutdoorClient client = new IndoorOutdoorClient(context, null);
client.startShortDetection(Trigger.POWER);
}
}
}
return Result.SUCCESS;
} }
This is how I initialize the work in my onCreate() method:
Constraints constraints = new Constraints.Builder().setRequiresCharging(true).build();
OneTimeWorkRequest simpleReuquest = new OneTimeWorkRequest.Builder(ChargerWorker.class)
.setConstraints(constraints)
.build();
WorkManager.getInstance().enqueue(simpleReuquest);
When I connect the device to the adapter for the first time, everything works as it should. However when I disconnect the device, and try again, I never reach the doWork function again.
Instead I see the following message in the logcat:
E/WorkerWrapper: Status for 031e39f1-bc10-4a35-9341-11453fc0ca21 is SUCCEEDED; not doing any work.
Is it because I use OneTimeWorkRequest? If so how can I schedule it to run every time the device is connected to a power source?
This is because you use OnTimeWorkRequest, from its name you can infer that the request is valid for one time only. Once triggered, it won't be called again, not unless you euqueue it again.
Problem is WorkManager doesn't support any other requests or constraints that are relevant to your need. Take a look at this question.
If you really want to use this Pattern of WorkManger and Requests, i can give you an idea, that relies on the fact that people tend to charge their phone for more than few minutes every time.
You can use a 2 types of WorkRequests combined:
OneTimeWorkRequest that is the actual logic of your algorithm.
Set the constraints to be only when charging.
Once triggered, this worker will enqueue a PeriodicWorkRequest without any constraints and "The minimum repeat interval that can be defined is 15 minutes"
PeriodicWorkRequest that that will be triggered every 15 minutes and periodically check if the phone is still charging.
The first time the worker detect that the device is unplugged, it enqueues the OneTimeWorkRequest from above and cancels itself.
This way you won't detect rapid changes of device power states, but it's the best possible in Android Oreo (and above) since apps that target the API level 26 or higher can no longer register broadcast receivers for implicit broadcasts in their manifest
To check whether your device is plugged or not:
You can even detect what kind of charging it is, use this code:
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
// Are we charging / charged?
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
// How are we charging?
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
taken right from android developer guide
To cancel a WorkRequest:
First, build it with a tag:
OneTimeWorkRequest.Builder(MyWorker.class)
.setConstraints(myConstraints)
.addTag(tag)
.build();
and then do WorkManager.getInstance().cancelAllWorkByTag(tag)
Another option is to enqueue a unique work:
WorkManager.getInstance().enqueueUniquePeriodicWork(
uniqueWorkName, ExistingPeriodicWorkPolicy.REPLACE, periodicWork);
and then cancel it with WorkManager.getInstance().cancelUniqueWork(workName)
You Should to use PeriodicWorkRequest that let your Work manager work many time on Time you Choose Fays Minutes Hours as you want

Android slows down foreground service when device sleeps

For a few days now im struggling with the following: I want to count movement patterns using the gravity sensor on an Android device while the screen is off. I am using a bound service that I start in the foreground (with a notification for Android 8 to keep it running) and everything works fine when the screen is on. Even if the App is not running in the foreground everything works fine.
However, as soon as the display is turned off, very strange things start to happen: The sensor data is still processed and movement counted to some extend but the results are very bad and inaccurate. Also, if the screen is turned on again, the app behaves very strange. Sometimes the app works as expected but sometimes it seems that old sensor events are processed delayed and counted later. The whole algorithm is not running smoothly from this time on. What is also interesting: If the device is plugged in and I observe everything with the console in Android Studio everything works just perfect, even if the screen is off. However, if the device is unplugged, the results become wrong again.
I tried a lot of things: Running everything on the main thread, on another thread, using an IntentService, setting the App on the whitelist for Doze, not sending data to the MainActivity while the screen is turned off, and followed this guide (I am developing on an Galaxy J5) - but nothing worked. It seems that either the SensorFusion algorithms of the Android system shut down in Standby even if there is a registered listener or that Samsung has some battery optimization running in the background and limits CPU operations. Is there something else that could cause this behavior? So running fine if the app is active, if it is in the background, but not when the device is in sleep?
Also maybe interesting to mention: I am developing a plugin for Cordova.
This is my code
Main.class
public class SensorPlugin extends CordovaPlugin implements ServiceClass.Delegate {
public void initialize() {
...
Intent serviceIntent = new Intent(applicationContext, ServiceClass.class);
applicationContext.bindService(serviceIntent, serviceConnection,
Context.BIND_AUTO_CREATE);
}
public void start() {
serviceClass.startMeasuring();
}
#Override
public void updateMovementCount(int count) {
...
};
}
Service.class
public class ServiceClass extends Service implements OtherClass.Delegate {
private volatile boolean isMeasuring;
private volatile double gravityX;
private volatile double gravityY;
private volatile double gravityZ;
public IBinder onBind(Intent intent) {
sensorManager = (SensorManager) getApplicationContext().
getSystemService(Context.SENSOR_SERVICE);
startForeground(1, buildNotification());
return mBinder;
}
public void startMeasuring() {
assert sensorManager != null;
Sensor gravity = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
sensorManager.registerListener(this, gravity,
SensorManager.SENSOR_DELAY_GAME);
isTracking = true;
SensorThread sensorThread = new SensorThread();
sensorThread();
}
#Override
public void onSensorChanged(SensorEvent event) {
gravityX = event.values[0];
gravityY = event.values[1];
gravityZ = event.values[2];
}
// This is an interface method from another class that I wrote
// (see below). For which I set this class as delegate and as soon
// as the other class finds a pattern in the data it calls this method
#Override
public void movementPatternDidChange(int count) {
// Usually I send this count to the main class with
// the same delegation pattern
delegate.updateMovementCount(count);
}
class SensorProcessingThread extends Thread {
Handler handler = new Handler();
// I use another runnable here, as I only want to process 10
// sensor events per second. The sensor manager usually returns
// way more which I don't need.
private Runnable sensorProcessingRunnable = new Runnable() {
public void run() {
otherClass.processMotionData(gravityX, gravityY, gravityZ);
if (isMeasuring) {
handler.postDelayed(this, 100);
}
}
};
#Override
public void run() {
if (!isMeasuring) {
return;
}
handler.postDelayed(sensorProcessingRunnable, 100);
}
}
}
Edit
I tested a lot in the meantime. I am now using the Apache Commons Primitives Collections for a better performance (instead of the large FastUtil which also caused this error in the past).
I also tested the app on two devices, a rather old LG G2 and the Galaxy J5. The problem is the same on both. So probably not manufacturer specific. The Android Studio Profiler reports a CPU usage of average 1-3% on both devices, so I assume an overload is likely not the cause. I also tested a TimerTask and Timer instead of a Runnable and Handler, which did not work as well.
What is also very interesting: I tried to debug the App over Wifi as explained here, and the App works absolutely fine, even if the device sleeps and the screen is turned off. It is so difficult to debug this issue, because the App behaves fine as soon as I am debugging it even without a cable attached. I have no idea what I could do else.
For everyone with a similar problem: I finally found the solution.
I think it is not well explained in the Android documentation, and not very intuitive either, but it is still necessary to set a partial wake lock to prevent the CPU from sleeping. This explains how to set a wake lock. A foreground service alone is not enough to keep a time-critical process running.

Android BLE Device Button press Alert in android

I am working with BLE device.I perform the code using android GATT and it scan device and also I send immediate alert in BLE device from mobile using writeCharacteristic and BLE device is beep.But now I want to Alert in android Mobile when I press BLE device button. That exactly I don't know how to do.
Thank You.
For this you have to listen for characteristics change. When you will press the BLE button, a characteristics must be change inside its hardware (set any flag value depends on hardware's firmware coding). When characteristics changed, characteristics method will called. You can perform required functionality there.
#Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic
characteristic)
{
Here we received ble button pressed event callback. Do stuff here.
}
For receiving characteristics change callback first you have to enable the notification like this.
BluetoothGattCharacteristic characteristic = gatt.getService(mServiceUuuid).getCharacteristic(mCharOneUuuid);
gatt.setCharacteristicNotification(characteristic, true);
List<BluetoothGattDescriptor> list = characteristic.getDescriptors();
for (int i = 0; i < list.size(); i++) {
BluetoothGattDescriptor desc = list.get(i);
desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(desc);
}

How to detect when a BLE device is not in range anymore?

I use a LeScanCallback (can not use the newer scan methods because I'm developing for api 18. Not that it matters, since the android 5.0+ apis don't offer this functionality either) to detect when a nearby BLE device is detected:
private BluetoothAdapter.LeScanCallback bleCallback = new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes) {
discoveredDevices.add(bluetoothDevice);
}
};
I am not pairing or connecting with the devices because that's not required, I simply want to see which devices are nearby.
I'm trying to make a service that, every 5 mins or so, calls a webserver to update which devices are nearby at that moment.
Tricky part is that the android device will be moving, so a bluetooth device that is nearby right now, might not be in 5 mins. In that case I need to remove it from discoveredDevices.
Ideally, I would like to receive a callback when a bluetooth device was in range before, but is not anymore. This callback doesn't exist though.
(I'm aware of the android.bluetooth.device.action.ACL_CONNECTED and android.bluetooth.device.action.ACL_DISCONNECTED broadcasts, but those are for when you connect to a bluetooth device, which I don't want.)
An option is to do a fresh scan every 5 mins, but you can't tell when all nearby devices have been discovered, so you would have to do a timed scan, e.g. scan for 5 seconds and then send the collected data to the webservice.
This sounds dirty and risky because you can never know for sure all nearby devices were discovered within the allotted time, so I would very much like to avoid doing it like that.
Is there another way to do this?
Edit
Some devices continuously report discovery of nearby bluetooth devices, even if they were already discovered before. If that functionality was universal I could solve my problem, however this is device specific.
My phone's bluetooth adapter for example only discovers nearby devices once. Some other devices I have tested with do continuously report the same nearby devices, but not all devices do, so I can't rely on that unfortunately.
This sounds dirty and risky because you can never know for sure all nearby devices were discovered within the allotted time, so I would very much like to avoid doing it like that.
That sounds like a reasonable assumption, but it's wrong.
Bluetooth low energy works in a particular way and BLE devices have some limits. For instance, they have a fixed range of possible advertising frequencies, ranging from 20 milliseconds to 10.24 seconds, in steps of 0.625 milliseconds. See here and here for more detailed information.
This means that it can take at most 10.24 seconds before a device will broadcast a new advertisement package. BLE devices generally, if not always, provide a way for their owner to adjust their advertising frequency, so the frequency can of course vary.
In cases where you are periodically collecting data about nearby devices, like yours, it is fine to use a scan with a fixed time limit, save that data somewhere, restart the scan, collect new data, compare with old data --> get results.
For example, if a device was found in scan 1 but not in scan 2, you can conclude that the device was in range, but is not anymore.
Same goes for the other way around: if a device was found in scan 4 but not in scan 3, it is a newly discovered device.
Finally, if a device was found in scan 5, was not found in scan 6, but was again found in scan 7, it is rediscovered and can be handled as such if need be.
Because I'm answering my own question here, I'll add the code that I used to implement this.
I have the scanning done in a background service, and communicate to other parts of the app using BroadcastReceivers. Asset is a custom class of mine that holds some data. DataManager is a custom class of mine that - how did you guess it - manages data.
public class BLEDiscoveryService extends Service {
// Broadcast identifiers.
public static final String EVENT_NEW_ASSET = "EVENT_NEW_ASSET ";
public static final String EVENT_LOST_ASSET = "EVENT_LOST_ASSET ";
private static Handler handler;
private static final int BLE_SCAN_TIMEOUT = 11000; // 11 seconds
// Lists to keep track of current and previous detected devices.
// Used to determine which are in range and which are not anymore.
private List<Asset> previouslyDiscoveredAssets;
private List<Asset> currentlyDiscoveredAssets;
private BluetoothAdapter bluetoothAdapter;
private BluetoothAdapter.LeScanCallback BLECallback = new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes) {
Asset asset = DataManager.getAssetForMACAddress(bluetoothDevice.getAddress());
handleDiscoveredAsset(asset);
}
};
#Override
public void onCreate() {
super.onCreate();
BluetoothManager manager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
bluetoothAdapter = manager.getAdapter();
previouslyDiscoveredAssets = new ArrayList<>();
currentlyDiscoveredAssets = new ArrayList<>();
handler = new Handler();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// Start scanning.
startBLEScan();
// After a period of time, stop the current scan and start a new one.
// This is used to detect when assets are not in range anymore.
handler.postDelayed(new Runnable() {
#Override
public void run() {
performRepeatingTask();
// Repeat.
handler.postDelayed(this, BLE_SCAN_TIMEOUT);
}
}, BLE_SCAN_TIMEOUT);
// Service is not restarted if it gets terminated.
return Service.START_NOT_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
handler.removeCallbacksAndMessages(null);
stopBLEScan();
super.onDestroy();
}
private void startBLEScan() {
bluetoothAdapter.startLeScan(BLECallback);
}
private void stopBLEScan() {
bluetoothAdapter.stopLeScan(BLECallback);
}
private void handleDiscoveredAsset(Asset asset) {
currentlyDiscoveredAssets.add(asset);
// Notify observers that we have a new asset discovered, but only if it was not
// discovered previously.
if (currentlyDiscoveredAssets.contains(asset) &&
!previouslyDiscoveredAssets.contains(asset)) {
notifyObserversOfNewAsset(asset);
}
}
private void performRepeatingTask() {
// Check if a previously discovered asset is not discovered this scan round,
// meaning it's not in range anymore.
for (Asset asset : previouslyDiscoveredAssets) {
if (!currentlyDiscoveredAssets.contains(asset)) {
notifyObserversOfLostAsset(asset);
}
}
// Update lists for a new round of scanning.
previouslyDiscoveredAssets.clear();
previouslyDiscoveredAssets.addAll(currentlyDiscoveredAssets);
currentlyDiscoveredAssets.clear();
// Reset the scan.
stopBLEScan();
startBLEScan();
}
private void notifyObserversOfNewAsset(Asset asset) {
Intent intent = new Intent();
intent.putExtra("macAddress", asset.MAC_address);
intent.setAction(EVENT_NEW_ASSET);
sendBroadcast(intent);
}
private void notifyObserversOfLostAsset(Asset asset) {
Intent intent = new Intent();
intent.putExtra("macAddress", asset.MAC_address);
intent.setAction(EVENT_LOST_ASSET);
sendBroadcast(intent);
}
}
This code is not perfect and might even be buggy, but it will at least give you an idea or example of how this can be implemented.
I can recommend this approach:
Use Map<BluetoothDevice, Long> structure to store the discovered devices, where Long is the time of detection of the device (can be System.currentTimeMillis() for example).
Then in your service (as far as I understand from the question there will be implemented some kind of repeated task) just extract actual devices based on the time of their detection.
And you are absolutely right, there are no guarantee that all nearby devices were discovered within the allotted time. Especially this is actual for the Android devices.
iOS devices in it's turn have another issue - they can change their BluetoothDevice's adress in runtime without apparent external cause.
Hope this will help you to save the time during debugging.
Edit
As a result of research of this topic found this discussion on code.google.com
Issue is still open and seems that it is related to the hardware features and can't be fixed programmatically. Moreover, it seems that bug will remains on problem devices even after a system updates.
So restarting the scan periodically might be acceptable workaround for this case.

Android 4.3: How to connect to multiple Bluetooth Low Energy devices

My Question is: Can Android 4.3 (client) have active connections with multiple BLE devices (servers)? If so, how can I achieve it?
What I did so far
I try to evaluate what throughput you can achieve using BLE and Android 4.3 BLE API. In addition I also try to find out how many devices can be connected and active at the same time. I use a Nexus 7 (2013), Android 4.4 as master and TI CC2540 Keyfob as slaves.
I wrote a simple server software for the slaves, which transmits 10000 20Byte packets through BLE notifications. I based my Android App on the Application Accelerator from the Bluetooth SIG.
It works well for one device and I can achieve around 56 kBits payload throughput at a Connection Interval of 7.5 ms. To connect to multiple slaves I followed the advice of a Nordic Employee who wrote in the Nordic Developer Zone:
Yes it's possible to handle multiple slaves with a single app. You would need to handle each slave with one BluetoothGatt instance. You would also need specific BluetoothGattCallback for each slave you connect to.
So I tried that and it partly works. I can connect to multiple slaves. I can also register for notifications on multiple slaves. The problem begins when I start the test. I receive at first notifications from all slaves, but after a couple Connection Intervals just the notifications from one device come trough. After about 10 seconds the other slaves disconnect, because they seem to reach the connection time-out. Sometimes I receive right from the start of the test just notifications from one slave.
I also tried accessing the attribute over a read operation with the same result. After a couple of reads just the answers from one device came trough.
I am aware that there are a few similar questions on this forum: Does Android 4.3 support multiple BLE device connections?, Has native Android BLE GATT implementation synchronous nature? or Ble multiple connection. But none of this answers made it clear for me, if it is possible and how to do it.
I would be very grateful for advice.
I suspect everyone adding delays is just allowing the BLE system to complete the action you have asked before you submit another one. Android's BLE system has no form of queueing. If you do
BluetoothGatt g;
g.writeDescriptor(a);
g.writeDescriptor(b);
then the first write operation will immediately be overwritten with the second one. Yes it's really stupid and the documentation should probably actually mention this.
If you insert a wait, it allows the first operation to complete before doing the second. That is a huge ugly hack though. A better solution is to implement your own queue (like Google should have). Fortunately Nordic have released one for us.
https://github.com/NordicSemiconductor/puck-central-android/tree/master/PuckCentral/app/src/main/java/no/nordicsemi/puckcentral/bluetooth/gatt
Edit: By the way this is the universal behaviour for BLE APIs. WebBluetooth behaves the same way (but Javascript does make it easier to use), and I believe iOS's BLE API also behaves the same.
Re visting the bluetooth-lowenergy problem on android: I am still using delays.
The concept: after every major action that provokes the BluetoothGattCallback (e.g. conenction, service discovery, write, read) a dealy is needed. P.S. have a look at Google example on BLE API level 19 sample for connectivity to understand how Broadcasts should be sent and get some general understanding etc...
Firstly, scan (or scan) for BluetoothDevices, populate the connectionQueue with desired devices and call initConnection().
Have a look on the following example.
private Queue<BluetoothDevice> connectionQueue = new LinkedList<BluetoothDevice>();
public void initConnection(){
if(connectionThread == null){
connectionThread = new Thread(new Runnable() {
#Override
public void run() {
connectionLoop();
connectionThread.interrupt();
connectionThread = null;
}
});
connectionThread.start();
}
}
private void connectionLoop(){
while(!connectionQueue.isEmpty()){
connectionQueue.poll().connectGatt(context, false, bleInterface.mGattCallback);
try {
Thread.sleep(250);
} catch (InterruptedException e) {}
}
}
Now if all is good, you have made connections and BluetoothGattCallback.onConnectionStateChange(BluetoothGatt gatt, int status, int newState) has been called.
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
switch(status){
case BluetoothGatt.GATT_SUCCESS:
if (newState == BluetoothProfile.STATE_CONNECTED) {
broadcastUpdate(BluetoothConstants.ACTION_GATT_CONNECTED, gatt);
}else if(newState == BluetoothProfile.STATE_DISCONNECTED){
broadcastUpdate(BluetoothConstants.ACTION_GATT_DISCONNECTED, gatt);
}
break;
}
}
protected void broadcastUpdate(String action, BluetoothGatt gatt) {
final Intent intent = new Intent(action);
intent.putExtra(BluetoothConstants.EXTRA_MAC, gatt.getDevice().getAddress());
sendBroadcast(intent);
}
P.S. sendBroadcast(intent) might need to be done like this:
Context context = activity.getBaseContext();
context.sendBroadcast(intent);
Then the broadcast is received by BroadcastReceiver.onReceive(...)
public BroadcastReceiver myUpdateReceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if(BluetoothConstants.ACTION_GATT_CONNECTED.equals(action)){
//Connection made, here you can make a decision: do you want to initiate service discovery.
// P.S. If you are working with multiple devices,
// make sure that you start the service discovery
// after all desired connections are made
}
....
}
}
After doing whatever you want in the broadcast receiver, here is how I continue:
private Queue<BluetoothGatt> serviceDiscoveryQueue = new LinkedList<BluetoothGatt>();
private void initServiceDiscovery(){
if(serviceDiscoveryThread == null){
serviceDiscoveryThread = new Thread(new Runnable() {
#Override
public void run() {
serviceDiscovery();
serviceDiscoveryThread.interrupt();
serviceDiscoveryThread = null;
}
});
serviceDiscoveryThread.start();
}
}
private void serviceDiscovery(){
while(!serviceDiscoveryQueue.isEmpty()){
serviceDiscoveryQueue.poll().discoverServices();
try {
Thread.sleep(250);
} catch (InterruptedException e){}
}
}
Again, after a successful service discovery, BluetoothGattCallback.onServicesDiscovered(...) is called. Again, I send an intent to the BroadcastReceiver (this time with different action String) and it is now that you can start reading, writing and enabling notifications/indications...
P.S. If you are working with multiple devices, make sure that you start the reading, writing etc... stuff after all devices have reported that their services have been discovered.
private Queue<BluetoothGattCharacteristic> characteristicReadQueue = new LinkedList<BluetoothGattCharacteristic>();
private void startThread(){
if(initialisationThread == null){
initialisationThread = new Thread(new Runnable() {
#Override
public void run() {
loopQueues();
initialisationThread.interrupt();
initialisationThread = null;
}
});
initialisationThread.start();
}
}
private void loopQueues() {
while(!characteristicReadQueue.isEmpty()){
readCharacteristic(characteristicReadQueue.poll());
try {
Thread.sleep(BluetoothConstants.DELAY);
} catch (InterruptedException e) {}
}
// A loop for starting indications and all other stuff goes here!
}
BluetoothGattCallback will have all your incoming data from the BLE sensor. A good practice is to send a broadcast with the data to your BroadcastReceiver and handle it over there.
I am developing an app with BLE features myself. The way I managed to connect to multiple devices and turn on notifications was to implement delays.
So I make a new thread (in order not to block UI thread) and in the new thread connect and turn on notifications.
For example, after BluetoothDevice.connectGatt(); call Thread.sleep();
And add the same delay for read/write and enable/dissable notifications.
EDIT
Use wait like this so that Android dindn't reaise ANR
public static boolean waitIdle() {
int i = 300;
i /= 10;
while (--i > 0) {
if (true)
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return i > 0;
}
Unfortunately notifications in the current Android BLE stack are a bit buggy. There are some hardcoded limits and I've found some stability issues even with a single device. (I read at one point that you could only have 4 notifications... not sure if that's across all devices or per device. Trying to find the source for that info now.)
I would try switching to a polling loop (say, poll the items in question 1/sec) and seeing if you find your stability increases. I would also consider switching to a different slave device (say a HRM or the TI SensorTag) to see if there is perhaps an issue with the slave-side code (unless you can test that against iOS or another platform and confirm it isn't part of the issue).
Edit: Reference for notification limitation
Rain is right in his answer, you need delays for pretty much everything when you work with BLE in Android. I developed several apps with it and it is really necessary. By using them you avoid a lot of crashes.
In my case, I use delays after every read/write command. Doing so, you ensure you receive the response from the BLE device almost always. I do something like this: (of course everything is done in a separate thread to avoid to much work on the main thread)
readCharacteristic(myChar);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
myChar.getValue();
or:
myChar.setValue(myByte);
writeCharacteristic(myChar);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
This is really useful when you read/write several characteristics in a row... As Android is enough fast to execute the commands almost instantly, if you don't use a delay between them you may get errors or incoherent values...
Hope it helps even if it is not exactly the answer to your question.

Categories

Resources