I've seen plenty of questions about how to connect to multiple devices purposely. But in my situation, I am only trying to connect to one hardware device.
I have two hardware devices that are supposed to do the same thing. When they connect to my app via BLE, then they have an LED that turns a solid color. This all works fine and dandy when I only have one device turned on. However, when I turn two of the devices on and then try to connect to just one. Both of the devices' LED's turn solid. Although I don't seem to be getting any incoming data from the one that I didn't intend to connect to.
I don't think it's the device's fault. Because I don't have this issue on iOS. I think the phone might be remembering previously connected devices somewhere maybe?
I'm sorry, this is a lot of code. But I feel like it's important to have this whole class. Any help is much appreciated.
package com.roberts.croberts.orange;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.bluetooth.le.BluetoothLeScanner;
import java.util.ArrayList;
import java.util.List;
#TargetApi(21)
public class BluetoothRegulator {
private static BluetoothRegulator instance = null;
private Context context;
private BluetoothLeScanner mLEScanner;
private BluetoothDevice orangeDevice;
//scanner stuff
private Handler mHandler;
// Stops scanning after 3 seconds.
private static final long SCAN_PERIOD = 3000;
//connected stuff
private android.bluetooth.BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothGatt mBluetoothGatt;
public ArrayList<BluetoothDevice> devices = new ArrayList<>();
private Handler foundHandler = new Handler();
private Handler servicesHandler = new Handler();
private ScanCallback mScanCallback;
public static BluetoothRegulator sharedInstance(){
if(instance == null) {
instance = new BluetoothRegulator();
Log.i("chase", "created new instance");
}
return instance;
}
// Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered.
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
mBluetoothGatt.discoverServices();
Log.i(TAG, "BR: onconnectionsStateChanged Connected to GATT server.");
// Attempts to discover services after successful connection.
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i(TAG, "Disconnected from GATT server.");
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// Loops through available GATT Serviceokay so ees.
for (BluetoothGattService gattService : mBluetoothGatt.getServices()) {
for (BluetoothGattCharacteristic gattCharacteristic : gattService.getCharacteristics()) {
mBluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
Log.i(TAG, mBluetoothGatt == null ? "mbluetooth is null" : "should be subscribed");
}
}
Log.i("chase", "did connect and discover devices");
} else {
Log.w(TAG, "Not Success onServicesDiscovered received: " + status);
connect(orangeDevice);
}
}
private Object getFieldFromObject(Object obj, String name){
try {
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(obj);
}catch(Exception e){
Log.i("chase", "e: "+e);
return null;
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Log.i("BR: chase", "received data!");
}
};
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
public boolean initialize(android.bluetooth.BluetoothManager btmanager, Context ctx) {
mBluetoothManager = btmanager;
context = ctx;
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
if (Build.VERSION.SDK_INT >= 21) {
mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
setUpCallBack();
}
return true;
}
public void scan() { //we call scan when they hit the connect button...
// Stops scanning after a pre-defined scan period.
Log.i("chase", "start scanning");
devices = new ArrayList<>();
if (mHandler == null) mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
}
}, SCAN_PERIOD);
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mLEScanner.startScan(mScanCallback);
}
}
private void foundDevice(BluetoothDevice device){
final String deviceName = device.getName();
if (deviceName != null && deviceName.length() > 5 && (deviceName.substring(0, 6).equals("orange") || deviceName.substring(0, 6).equals("smartb"))) {
for (BluetoothDevice d : devices){
if (d.getAddress().equals(device.getAddress())){
return;
}
}
mHandler.removeCallbacksAndMessages(null);
devices.add(device);
if (devices.size() == 1) { //wait one second and then assume there aren't any more devices named "orange"
foundHandler.postDelayed(new Runnable() {
public void run() {
doneSearching();
}
}, 1000);
}
}
}
private void doneSearching(){
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
if (devices.size() == 1){
BluetoothDevice device = devices.get(0);
connect(device);
}else{
//normally this displays a list and the user can choose which device. But this works just as well for now.
BluetoothDevice device = devices.get(0);
connect(device);
}
}
//connect method
public boolean connect(BluetoothDevice btdevice) {
orangeDevice = btdevice;
if (mBluetoothAdapter == null || btdevice == null || btdevice.getAddress() == null) {
return false;
}
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLEScanner.stopScan(mScanCallback);
}
devices = new ArrayList<>();
mBluetoothGatt = orangeDevice.connectGatt(context, true, mGattCallback);
return true;
}
public void disconnect() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) return;
mBluetoothGatt.disconnect();
mBluetoothGatt = null;
}
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
foundDevice(device);
}
};
public void setUpCallBack(){
mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice device = result.getDevice();
foundDevice(device);
}
#Override
public void onScanFailed(int errorCode) {
Log.e("Chase", "Scan Failed Error Code: " + errorCode);
}
};
}
}
Update
I was playing around with a galaxy tablet and wasn't able to recreate the issue. So I think it's device dependent. The problem occurs on the Galaxy S3, and I am trying to round up some other devices to test with.
Also, I was able to get my hands on some new devices and it seems that if the device has never been connected before to the phone (virgin device) then that device doesn't get mixed up and think it's connected when it's not. So we will see it when we search but it never thinks I am connecting to it, until I actually do connect to it. After that, then half the time it thinks I am trying to talk to it when I am not. I hope that makes sense. Which backs up the theory that the phone is somehow caching old devices. I tried uninstalling the app and reinstalling it to see if it would have the same effect as using a virgin device, but it seems the app has nothing to do with it. The device will still connect (when it's not supposed to) after it has been introduced to the phone, even if I did a fresh install of the app.
I would check the BLE devices themselves. Is there a chance that they might have the same System ID ? I believe it's the first characteristic in 0x180A. If so - it will be difficult for the host to distinguish them and such a double connection might happen.
-Lets say you have 2 devices. So foundDevice() gets called. Now devices arraylist contains 1 device.
-After that you are using handler which calls doneSearching() & checks
if device.size()==1
It returns true and you call connect()
-Inside connect you are again creating an arraylist i.e
devices = new ArrayList<>();
So what happens now is your devices ArrayList<>() contains 0 elements.
-So now when 2nd device is found again the above steps are repeated because whenever connect method is getting called, the size of list is getting refreshed to 0
So just remove the line
devices = new ArrayList<>();
inside connect() method
Related
I have faced with the issue using startScan method of BluetoothLeScanner a BLE device was found, but when I turned off BLE device my phone still shows this device as turned on !!
I have tried to use:
private ScanCallback mScanCallback = new ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
Log.i("ScanCallback", String.format("onScanResult(int callbackType[%d], ScanResult result)", callbackType));
final BluetoothDevice btDevice = result.getDevice();
if (btDevice == null){
Log.e("ScanCallback", "Could not get bluetooth device");
return;
}
final String macAddress = btDevice.getAddress();
if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST) {
// NOTE: I've never got here
final BluetoothDevice outOfRangeDevice = mBtDevices.get(macAddress);
...
} else {
...
}
}
...
};
Guy, I have not found solution how to detect that BLE device is lost in other resources like (Android SDK reference, forums, stackoverflow and etc) (:
Any help will be appreciated !!
During googling and exploring the Android Documentations I have figured out how to detect if device is out of range. I would like to share my solution how I did it:
...
public void scanBLEDevices(final boolean enable) {
if(mLeScanner == null) {
Log.d(TAG, "Could not get LEScanner object");
throw new InternalError("Could not get LEScanner object");
}
if (enable) {
startLeScan();
} else {
stopLeScan(false);
}
}
private void startLeScan() {
Log.i(TAG, "startLeScan(BluetoothLeScanner mLeScanner)");
mScanning = true;
mInRangeBtDevices.clear();
if (mStartScanCallback != null) {
mHandler.removeCallbacks(mStartScanCallback);
}
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mLeScanner.startScan(mScanFilters, mScanSettings, mScanCallback);
}
mStopScanCallback = new Runnable() {
#Override
public void run() {
stopLeScan(true);
}
};
mHandler.postDelayed(mStopScanCallback, SCAN_PERIOD);
}
private void stopLeScan(final boolean isContinueAfterPause) {
Log.i(TAG, "stopLeScan(BluetoothLeScanner mLeScanner)");
mScanning = false;
if (mStopScanCallback != null) {
mHandler.removeCallbacks(mStopScanCallback);
}
removeOutOfRangeDevices();
if (Build.VERSION.SDK_INT < 21) {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
} else {
mLeScanner.stopScan(mScanCallback);
}
if (isContinueAfterPause) {
mStartScanCallback = new Runnable() {
#Override
public void run() {
startLeScan();
}
};
mHandler.postDelayed(mStartScanCallback, SCAN_PAUSE);
}
}
private void removeOutOfRangeDevices() {
final Set<String> outOfRangeDevices = new HashSet<>();
for (String btAddress : mBtDevices.keySet()) {
if (!mInRangeBtDevices.contains(btAddress)) {
outOfRangeDevices.add(btAddress);
}
}
for (String btAddress : outOfRangeDevices) {
final BluetoothDevice outOfRangeDevice = mBtDevices.get(btAddress);
mBtDevicesRSSI.remove(btAddress);
mBtDevices.remove(btAddress);
}
}
...
Explanation:
As you can see I have added on each scanning period mInRangeBtDevices collection that will keep all devices found during the current scanning.
When I stop scanning, I am also removing out of range device from previous lists that is not available anymore using one additional helper collection outOfRangeDevices
I think this example would be usefull and you will be able to integrate it in your own code
This one is looking good (JAVA):
As I understood, you need to implement startLeScan().
Find BLE devices
To find BLE devices, you use the startLeScan() method. This method takes a BluetoothAdapter.LeScanCallback as a parameter. You must implement this callback, because that is how scan results are returned. Because scanning is battery-intensive, you should observe the following guidelines:
As soon as you find the desired device, stop scanning.
Never scan on a loop, and set a time limit on your scan. A device that was previously available may have moved out of range, and continuing to scan drains the battery.
The following snippet shows how to start and stop a scan:
public class DeviceScanActivity extends ListActivity {
private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
...
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
...
}
...}
Consider checking this tutorial as well.
Also this one.
I am working on BLE connection in my app. Everything works fine until I press the back button. On pressing the back button, the BLE connection should disconnect and again on activity loading It tries to connect to the tool. While the activity loads, I still get gatt != null as true. I am unable to findout the issue. I think there are some issues in my code in onConnectionStateChanged(), Service disconnected. I will post my code below. Please have a look.
llBack.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
FragmentManager fragmentManager = mContext.getSupportFragmentManager();
BleUtil.disconnect_tool();
fragmentManager.popBackStack();
mContext.recreate();
}
In side BleUtil class:
public static void disconnect_tool()
{
mBluetoothGatt.disconnect();
}
Inside OnConnectionStateChaged():
private BluetoothGattCallback gattCallback =
new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
{
if (newState == STATE_CONNECTED) {
gattInterface.onToolConnected();
gatt.discoverServices();
Toast.makeText(activity,"Connected",Toast.LENGTH_SHORT).show();
} else if (newState == STATE_DISCONNECTED) {
//disconnect_tool();
gatt.close();
mBluetoothGatt.close();
gatt = null;
mBluetoothGatt = null;
gattInterface.onToolDisconnected();
Log.d("checkdisco","disconn");
// mConnectionState = STATE_DISCONNECTED;
Toast.makeText(activity,"Disconnected",Toast.LENGTH_SHORT).show();
// gatt.close();
if(gatt != null) {
gatt.close();
gatt = null;
}
}
}
#Override
public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
if (status == BluetoothGatt.GATT_SUCCESS) { gattInterface.onservicefound(gatt);}
I think Android does not actually disconnect the BLE link, but rather stops propagating BLE events to your application. This is just something I read once somehwere so take it with a grain of salt.
I have 2 Android applications. One of them advertises a UUID via bluetooth, and provides write characteristics. The other one scans for a device advertising this UUID, connects to it, and then writes to its 2 characteristics.
Basically, I need the device/app that is advertising the UUID (I'll call it the "server app" now) to always know when the other device/app is present, as well as its UUID that I am writing to. I have to have 2 write characteristics for the UUID, because it's too long to fit into 1, so I write the first half of the UUID in the first characteristic, and the second half in the second characteristic.
So the flow of the apps are as such:
The "server app" advertises a UUID
The other app scans for the advertising UUID
The devices come within Bluetooth range
The app that is scanning finds the advertising UUID, and connects to the device.
The app then writes its own UUID to the 2 characteristics.
The "server app" receives the UUID and displays it on the screen.
The app continuously writes its UUID to the 2 characteristics while within Bluetooth range, even after being separated out of range and then brought back within range.
What I have working is the first 6 steps. I can't get step 7 to work.
The problem that I have, is after the UUID is written to the 2 characteristics, it won't write to it anymore. So the "server app" won't know that the other device/app is present anymore or not. I have to terminate the app (that does the scanning) and restart it so that it can connect to the other device/app and write to the characteristics again.
I need to continuously write to the characteristics when they are near each other (with some delay in between is fine). Even after the devices are separated in distance greater than the Bluetooth range, and then brought back together. I wrote these apps in Xamarin, but I think it applies to Android in general. Here is my code for the app that advertises the UUID:
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
private static Java.Util.UUID AdvertiseUUID = Java.Util.UUID.FromString("59ed0f55-e984-4fc2-9403-b1e64269ec5e");
private static Java.Util.UUID ServiceUUID = Java.Util.UUID.FromString("89c9a1f2-5c38-492d-b8e9-67830268682e");
private static Java.Util.UUID WriteCharacteristic1UUID = Java.Util.UUID.FromString("92515af8-8d70-40a7-b5b1-5a5bd624e5a0");
private static Java.Util.UUID WriteCharacteristic2UUID = Java.Util.UUID.FromString("71d640cb-bb78-45bd-ae26-614fead76efe");
BluetoothLeAdvertiser advertiser;
BluetoothGattServer gattServer;
BluetoothGattService service;
MyAdvertiseCallback callback = new MyAdvertiseCallback();
private static string writeText1;
private static string writeText2;
private static List<PassengerDevice> passengerDevices = new List<PassengerDevice>();
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
AndroidBluetoothServer();
}
private async Task AndroidBluetoothServer()
{
try
{
CreateGattServer();
advertiser = BluetoothAdapter.DefaultAdapter.BluetoothLeAdvertiser;
var advertiseBuilder = new AdvertiseSettings.Builder();
var parameters = advertiseBuilder.SetConnectable(true)
.SetAdvertiseMode(AdvertiseMode.LowLatency)
.SetTxPowerLevel(AdvertiseTx.PowerHigh)
.Build();
AdvertiseData data = (new AdvertiseData.Builder()).AddServiceUuid(new ParcelUuid(AdvertiseUUID)).Build();
advertiser.StartAdvertising(parameters, data, callback);
}
catch(Exception e)
{
}
}
private void CreateGattServer()
{
service = new BluetoothGattService(ServiceUUID, GattServiceType.Primary);
BluetoothGattCharacteristic writeCharacteristic1 = new BluetoothGattCharacteristic(WriteCharacteristic1UUID, GattProperty.WriteNoResponse, GattPermission.Write);
BluetoothGattCharacteristic writeCharacteristic2 = new BluetoothGattCharacteristic(WriteCharacteristic2UUID, GattProperty.WriteNoResponse, GattPermission.Write);
service.AddCharacteristic(writeCharacteristic1);
service.AddCharacteristic(writeCharacteristic2);
var bluetoothManager = GetSystemService(BluetoothService) as BluetoothManager;
gattServer = bluetoothManager.OpenGattServer(BaseContext, new MyGattServerCallback());
gattServer.AddService(service);
}
public class MyGattServerCallback : BluetoothGattServerCallback
{
public override void OnCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, bool preparedWrite, bool responseNeeded, int offset, byte[] value)
{
base.OnCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
var str = System.Text.Encoding.Default.GetString(value);
var passengerDevice = passengerDevices.FirstOrDefault(d => d.Address == device.Address);
if(passengerDevice != null)
{
if (characteristic.Uuid == WriteCharacteristic1UUID)
{
passengerDevice.Text1 = str;
}
else if (characteristic.Uuid == WriteCharacteristic2UUID)
{
passengerDevice.Text2 = str;
passengerDevice.RecievedTimestamp = DateTime.Now;
}
}
else
{
var newDevice = new PassengerDevice();
newDevice.Address = device.Address;
if (characteristic.Uuid == WriteCharacteristic1UUID)
{
newDevice.Text1 = str;
}
else if (characteristic.Uuid == WriteCharacteristic2UUID)
{
newDevice.Text2 = str;
}
passengerDevices.Add(newDevice);
}
App2.Class1.SetText(passengerDevices);
}
}
public class MyAdvertiseCallback : AdvertiseCallback
{
public override void OnStartFailure([GeneratedEnum] AdvertiseFailure errorCode)
{
base.OnStartFailure(errorCode);
}
public override void OnStartSuccess(AdvertiseSettings settingsInEffect)
{
base.OnStartSuccess(settingsInEffect);
}
}
protected override void OnDestroy()
{
try
{
if(advertiser != null)
{
advertiser.StopAdvertising(callback);
advertiser.Dispose();
advertiser = null;
}
if (service != null)
{
service.Dispose();
service = null;
}
if (gattServer != null)
{
gattServer.Close();
gattServer.Dispose();
gattServer = null;
}
}
catch(Exception e)
{
}
finally
{
base.OnDestroy();
}
}
}
And here is my code for the app that scans for the UUID and writes to the characteristics:
public class AndroidBluetooth
{
private static Java.Util.UUID AdvertiseUUID = Java.Util.UUID.FromString("59ed0f55-e984-4fc2-9403-b1e64269ec5e");
private static Java.Util.UUID ServiceUUID = Java.Util.UUID.FromString("89c9a1f2-5c38-492d-b8e9-67830268682e");
private static Java.Util.UUID WriteCharacteristic1UUID = Java.Util.UUID.FromString("92515af8-8d70-40a7-b5b1-5a5bd624e5a0");
private static Java.Util.UUID WriteCharacteristic2UUID = Java.Util.UUID.FromString("71d640cb-bb78-45bd-ae26-614fead76efe");
private ScanCallback _scanCallback;
private ScanSettings _scanSettings;
private ScanFilter _scanFilter;
private MyBluetoothGattCallback _gattCallback;
private static ParcelUuid _parcelUuid;
public static bool IsStarted { get; private set; }
public void StartAndroidBluetoothClient()
{
IsStarted = true;
_parcelUuid = new ParcelUuid(AdvertiseUUID);
_gattCallback = new MyBluetoothGattCallback();
_scanCallback = new MyScanCallback() { GattCallback = _gattCallback };
_scanFilter = new ScanFilter.Builder().SetServiceUuid(new ParcelUuid(AdvertiseUUID))
.Build();
_scanSettings = new ScanSettings.Builder().SetScanMode(Android.Bluetooth.LE.ScanMode.LowLatency)
.Build();
BluetoothAdapter.DefaultAdapter.BluetoothLeScanner.StartScan(new List<ScanFilter>() { _scanFilter }, _scanSettings, _scanCallback);
}
public class MyScanCallback : ScanCallback
{
public MyBluetoothGattCallback GattCallback { get; set; }
public override void OnScanResult([GeneratedEnum] ScanCallbackType callbackType, ScanResult result)
{
base.OnScanResult(callbackType, result);
result.Device.ConnectGatt(Android.App.Application.Context, true, GattCallback);
}
public override void OnScanFailed([GeneratedEnum] ScanFailure errorCode)
{
base.OnScanFailed(errorCode);
}
}
public class MyBluetoothGattCallback : BluetoothGattCallback
{
public BluetoothGatt Gatt { get; set; }
public override void OnConnectionStateChange(BluetoothGatt gatt, [GeneratedEnum] GattStatus status, [GeneratedEnum] ProfileState newState)
{
base.OnConnectionStateChange(gatt, status, newState);
// It is Success the first time, but then never again until I kill the app and restart it
if (status == GattStatus.Success)
{
Gatt = gatt;
gatt.DiscoverServices();
}
}
public override void OnServicesDiscovered(BluetoothGatt gatt, [GeneratedEnum] GattStatus status)
{
base.OnServicesDiscovered(gatt, status);
if (status == GattStatus.Success)
{
var service = gatt.GetService(ServiceUUID);
if (service != null)
{
var wc1 = service.GetCharacteristic(WriteCharacteristic1UUID);
if (wc1 != null)
{
wc1.SetValue("71d640cb-bb78-45bd");
gatt.WriteCharacteristic(wc1);
}
}
}
}
public override void OnCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, [GeneratedEnum] GattStatus status)
{
base.OnCharacteristicWrite(gatt, characteristic, status);
if (status != GattStatus.Success)
{
// try again
if (characteristic.Uuid.Equals(WriteCharacteristic1UUID))
{
var service = gatt.GetService(ServiceUUID);
var wc1 = service.GetCharacteristic(WriteCharacteristic1UUID);
if (wc1 != null)
{
wc1.SetValue("71d640cb-bb78-45bd");
gatt.WriteCharacteristic(wc1);
}
}
else if (characteristic.Uuid.Equals(WriteCharacteristic2UUID))
{
var service = gatt.GetService(ServiceUUID);
var wc2 = service.GetCharacteristic(WriteCharacteristic2UUID);
if (wc2 != null)
{
wc2.SetValue("-ae26-614fead76efe");
gatt.WriteCharacteristic(wc2);
}
}
return;
}
if (characteristic.Uuid.Equals(WriteCharacteristic1UUID))
{
// now send the second text
var service = gatt.GetService(ServiceUUID);
var wc2 = service.GetCharacteristic(WriteCharacteristic2UUID);
if (wc2 != null)
{
wc2.SetValue("-ae26-614fead76efe");
gatt.WriteCharacteristic(wc2);
}
}
}
}
public void Close()
{
if(_gattCallback.Gatt != null)
{
// Some devices (or Android versions) will disconnect automatically when the app closes, but some won't and then you'll have to power off the phone to make sure the hardware is disconnected, and you won't be able to connect again until you do.
_gattCallback.Gatt.Close();
_gattCallback.Gatt.Disconnect();
_gattCallback.Gatt = null;
}
IsStarted = false;
}
}
I write to the 2 characteristics, one after the other, and then it never writes to them again until I kill and restart the app...how can I make it continuously write to the characteristics when the devices are within range?
You have some big question there, but I read through it all and I'll give you some pointers on where to go. For a full app most of those stuff would be IMHO in separate classes, taking care of just 1 responsibility, but I'll try my best:
" I need the (...) "server app" (...) to always know when the other device/app is present,"
for that you can/should use callback from BluetoothGattServerCallback.onConnectionStateChange() . With that you can know when a device connects and disconnects without needing this convoluted "always write" logic.
from what I understood on the client app you want:
while(true) {
while(not found) {
scan()
}
connect()
while(is connected) {
write characteristic 1
write characteristic 2
}
}
but from your code it seems you're missing the while parts. You're certainly missing inside OnCharacteristicWrite the part to keep repeating. Something like this:
// if I just wrote the first
if (characteristic.Uuid.Equals(WriteCharacteristic1UUID))
{
// write the sencond
var service = gatt.GetService(ServiceUUID);
var wc2 = service.GetCharacteristic(WriteCharacteristic2UUID);
if (wc2 != null)
{
wc2.SetValue("-ae26-614fead76efe");
gatt.WriteCharacteristic(wc2);
}
}
// if we just wrote the second
else if (characteristic.Uuid.Equals(WriteCharacteristic2UUID))
{
//write the 1st again
... insert here code that writes the 1st characteristic
}
It seems to it's missing some disconnection handling code. This should goinside the OnConnectionStateChange.
You have to do something like this:
if (status != GattStatus.Success || newState != CONNECTED)
{
// we're not connected, close the gatt
try{ gatt.disconnect() } catch(exception) { }
gatt.close()
}
else
{
Gatt = gatt;
gatt.DiscoverServices();
}
Android have some limitations on this gatt, so it's very important to disconnect and close it, or else you'll run into some system limitations.
I've never used this auto-connect option true, but it seems to me that you never stopped scanning. That means that you'll keep receiving callbacks in OnScanResult, so you keep asking to connect again and again.
You should certainly make some flag that device is already found and there's no need to connect again. Like:
onScanResul(...
if(!connectedOrConnectin) {
connectedOrConnectin = true
-> connect
// make sure to clear the flag after disconnection.
}
Also, you might want to not use the "auto-connect" flag to True (https://issuetracker.google.com/issues/37071781). It seems that you will get a much faster connection if trying to connect by the other method.
I hope it helps.
I currently have a method which writes to the BLE devices to beep it. My Bluetooth Callback goes as follows :
public class ReadWriteCharacteristic extends BluetoothGattCallback {
public ReadWriteCharacteristic(Context context, String macAddress, UUID service, UUID characteristic, Object tag, Activity activity) {
mMacAddress = macAddress;
mService = service;
mCharacteristic = characteristic;
mTag = tag;
mContext = context;
this.activity =activity;
final BluetoothManager bluetoothManager =
(BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
}
final private static String TAG = "ReadCharacteristic";
private Object mTag;
private String mMacAddress;
private UUID mService;
private BluetoothManager mBluetoothManager = null;
private BluetoothAdapter mBtAdapter = null;
private BluetoothGatt mBluetoothGatt = null;
private String mBluetoothDeviceAddress ="";
private UUID mCharacteristic;
BluetoothDevice device;
private Activity activity;
private BluetoothAdapter mBluetoothAdapter;
private Context mContext;
ReadWriteCharacteristic rc;
private int retry = 5;
public String getMacAddress() {
return mMacAddress;
}
public UUID getService() {
return mService;
}
public UUID getCharacteristic() {
return mCharacteristic;
}
public byte[] getValue() { return mValue; }
public void onError() {
Log.w(TAG, "onError");
}
public void readCharacteristic(){
if (retry == 0)
{
onError();
return;
}
retry--;
device = mBluetoothAdapter.getRemoteDevice(getMacAddress());
mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
int connectionState = mBluetoothManager.getConnectionState(device,
BluetoothProfile.GATT);
if (device != null) {
if (connectionState == BluetoothProfile.STATE_DISCONNECTED)
{
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null
&& getMacAddress().equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.w(TAG, "Re-use GATT connection");
if (mBluetoothGatt.connect()) {
Log.w(TAG, "Already connected, discovering services");
mBluetoothGatt.discoverServices();
//return ;
} else {
Log.w(TAG, "GATT re-connect failed.");
return;
}
}
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return;
}
Log.w(TAG, "Create a new GATT connection.");
rc= ReadWriteCharacteristic.this;
Log.w(TAG, "Starting Read [" + getService() + "|" + getCharacteristic() + "]");
mBluetoothGatt = device.connectGatt(mContext, false, rc);
refreshDeviceCache(mBluetoothGatt);
mBluetoothDeviceAddress = getMacAddress();
} else {
Log.w(TAG, "Attempt to connect in state: " + connectionState);
if(mBluetoothGatt!=null)
mBluetoothGatt.close();
readCharacteristic();
}
}
}
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.w(TAG,"onConnectionStateChange [" + status + "|" + newState + "]");
if ((newState == 2)&&(status ==0)) {
gatt.discoverServices();
}
else if(status == 133 )
{
//gatt.disconnect();
gatt.close();
mBluetoothGatt = null;
try
{
Thread.sleep(2000);
}
catch(Exception e)
{
}
readCharacteristic();
}
else{
if(mBluetoothGatt!=null)
mBluetoothGatt.close();
// gatt.close();
Log.w(TAG, "[" + status + "]");
//gatt.disconnect();
try
{
Thread.sleep(2000);
}
catch(Exception e)
{
}
mBluetoothGatt = null;
readCharacteristic();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.w(TAG,"onServicesDiscovered [" + status + "]");
BluetoothGattService bgs = gatt.getService(getService());
if (bgs != null) {
BluetoothGattCharacteristic bgc = bgs.getCharacteristic(getCharacteristic());
gatt.readCharacteristic(bgc);
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Log.w(TAG,"onCharacteristicWrite [" + status + "]");
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.w(TAG,"onCharacteristicWrite [" + getDataHex(characteristic.getValue()) + "]");
// gatt.disconnect();
if(mBluetoothGatt!=null)
mBluetoothGatt.close();
// gatt.close();
// mBluetoothGatt=null;
}
else if(status ==133)
{
gatt.close();
try
{
Thread.sleep(2000);
}
catch(Exception e)
{
}
readCharacteristic();
}
else{
//gatt.disconnect();
gatt.close();
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
Log.w(TAG,"onCharacteristicRead [" + status + "]");
if (status == BluetoothGatt.GATT_SUCCESS) {
mValue = characteristic.getValue();
// Perform write operations
gatt.writeCharacteristic(bgc);
}
else if(status ==133)
{
gatt.close();
try
{
Thread.sleep(2000);
}
catch(Exception e)
{
}
readCharacteristic();
}
else {
// gatt.disconnect();
gatt.close();
}
}
}
This code works perfectly on device running Kitkat and below. But on Devices running Lollipop, this code works fine for the first instance. But from the next instance, irrespective of whether I disconnect or close the connection and try again, it just does not work. It keeps giving me a status code of 257 in onConnectionStateChange method. As far as I know, The Bluetooth GATT methods are the same for both kitkat and Lollipop devices.
What surprises me is that this code works fine on Lollipop devices when i use the old BLE API i.e startLeScan ( For eg - mBluetoothAdapter.startLeScan(mLeScanCallback);). This problem only arises when I use the new BLE API i.e BluetoothLeScanner ( scanner.startScan(filters, settings, new scancallback());). The scanning rate is really slow for Lollipop devices using the old BLE API, hence I cannot use it. I just don't understand how to solve this problem.Has anyone faced the same problem and found a solution? Any help would be deeply appreciated.
Quite a few things I would change here. Create a class variable for the data you want to read from the characteristic, such as private string heartRate;
1) You don't need the readCharacteristic() method. Instead, in the onConnectionStateChange once the device has connected properly, call mBluetoothGatt.discoverServices(). Then in the onServicesDiscovered() method I would call gatt.getServices(). Then use a foreach loop and loop through the returned services and compare the UUID of the service until you find the one you care about. Then if heartRate == null, call service.getCharacteristic(HeartRateUUID) and then read the characteristic. In onCharacteristicRead() check if the UUID is equal to the heart rate characteristic. If it is, assign the value of the characteristic to the heartRate variable. If you are interested, I can type out the methods or provide pseudocode.
2) I wouldn't call gatt.connect() followed by gatt.discoverServices(). gatt.connect() will reconnect to the current device as soon as it sees an advertisement packet from the device. I would call gatt.connect() and then call gatt.discoverServices() in the onConnectedStateChange() method.
3) In the onConnectedStateChange method don't use the gatt variable. Use mBluetoothGatt instead. mBluetoothGatt.disconnect() disconnects from the currently connected device. mBluetoothGatt.close() terminates the gatt instance. You cannot call mBluetoothGatt.connect() after calling mBluetoothGatt.Close(). This might not be needed, but if the device is connected I call mBluetoothGatt.disconnect() followed by mBluetoothGatt.close().
4) You can also chain characteristic readings together. In onCharacteristicRead(), after you get the value of the heartRate, you can immediately call characteristic.getService().getCharacteristic(UUIDTemperature) and then read that characteristic. It will call the OnCharacteristicRead method again.
Let me know if you want me to clarify anything; I'm typing on the crappy Surface Pro 3 keyboard. :)
I am keep on getting the NoClassDefFoundError when my class is loaded.
The code is taken from BluetoothLeGatt project -
http://developer.android.com/samples/BluetoothLeGatt/project.html
My code:
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() { //java.lang.NoClassDefFoundError...
#Override
public void onLeScan(final BluetoothDevice device,
final int rssi, final byte[] scanRecord) {
runOnUiThread(new Runnable() {
#Override
public void run() {
String msg= device.getAddress();
Log.d(TAG,msg);
addItems(msg);
}
});
}
};
Someone suggested that the error is because my device doesn't support BLE but I want to get rid of this error for any device. So if it doesn't support BLE feature then simply skip this error else continue with the call to this BluetoothAdapter.LeScanCallback.
NOTE:
See this my previous SO post for more clarification.
Putting the BLE feature check as the first line onCreate() doesn't stop the crash --
#Override
public void onCreate(Bundle savedInstanceState) {
if (!bleCheck()) {
Toast.makeText(getApplicationContext(), R.string.ble_not_supported,
Toast.LENGTH_SHORT).show();
finish();
}
//Rest of the code
//Call to BLE Scan on button click that causes error..
}
private boolean bleCheck() {
boolean result = false;
if (getPackageManager().
hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){
result = true;
}
return result;
}
As BluetoothAdapter.LeScanCallback was Added in API level 18 ; Source, this code would need a API level check also. Here's how i have gone about this, not declared the callback as private but under the condition:
boolean apiJBorAbove = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2 ? true
: false;
boolean isBleAvailable = getPackageManager().hasSystemFeature(
PackageManager.FEATURE_BLUETOOTH_LE) ? true : false;
// Use this check to determine whether BLE is supported on the device.
if (isBleAvailable && apiJBorAbove) {
// Initializes a Bluetooth adapter.
// For API level 18 and above, get a reference to
// BluetoothAdapter through BluetoothManager.
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
// Ensures Bluetooth is available on the device and it is enabled.
// If not, displays a dialog requesting user permission to enable
// Bluetooth.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
startActivity(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE));
}
BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device,
final int rssi, final byte[] scanRecord) {
runOnUiThread(new Runnable() {
#Override
public void run() {
String msg= device.getAddress();
// Log.d(TAG,msg);
// addItems(msg);
}
});
}
};
}
The NoClassDefFoundError is due to the API , not on the basis of PackageManager.FEATURE_BLUETOOTH_LE.