I am working with BLE connection in my app. I have a single class for the bluetooth functionalities and I am passing a command from a different fragment class for writing any value.
So based on the scenario, inside the fragment, on click of a button will send a write command to the bluetooth class. Its working fine in the first time and I am getting the response. But while clicking the button the button for the second time, onCharacteristicChanged() is called twice and the third time click makes it being called thrice and so on. I genuinely can't figure it out. I will post my code below. Please have a look. In case of any queries, please do let me know. Thanks in advance.
I am writing data inside the OnDescriptorWrite() where as recieving data inside
onCharacteristicChanged().
Inside the fragment:
rvw_availableList.addOnItemTouchListener((
new RecyclerItemClickListener(myContext, new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position)
{
BluetoothDevice bl_device = al_deviceList.get(position);
bleUtil.writeData(bl_device,"3AxxxxxxD");
}
})
));
Now inside the writeData() of BleUtil Class:
public void writeData(BluetoothDevice bluetoothDevice, final String value)
{
BluetoothGattCallback gattCallback =
new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
gatt.discoverServices();
}
#Override
public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
activity.runOnUiThread(new Runnable() {
public void run() {
// prgd_progress.HideProgressDialog();
Log.d("onServicesDiscovered", "Service uuid ");
List<BluetoothGattService> gattServices = gatt.getServices();
Log.d("onServicesDiscovered", "Services count: "+gattServices.size());
for (BluetoothGattService gattService: gattServices)
{
Log.d("aniservice",gattService.getUuid().toString());
}
if (status == BluetoothGatt.GATT_SUCCESS) {
ArrayList<String> alst_uuid = new ArrayList<String>();
BluetoothGattCharacteristic characteristic =
gatt.getService(UUID.fromString(SERVICE_ID)).getCharacteristics().get(0);
Log.d("anicheck",characteristic.getUuid().toString());
Log.d("anicheck",characteristic.getDescriptors().get(0).getUuid().toString());
// BluetoothGattCharacteristic characteristic =
// gattServices.get(0).getCharacteristics().get(0);
// Log.d("foundoutchar",gattServices.get(0).getUuid()+" "+gattServices.get(0).getCharacteristics().get(0).getUuid()+"");
gatt.setCharacteristicNotification(characteristic,true);
for (BluetoothGattDescriptor descriptor:characteristic.getDescriptors()){
Log.e("anicheck", "BluetoothGattDescriptor: "+descriptor.getUuid().toString());
}
final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if(descriptor!= null)
{
descriptor.setValue(
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
}
else
{
Toast.makeText(activity,"nullval", Toast.LENGTH_SHORT).show();
}
// Log.d("foundoutchar", descriptor.getUuid().toString());
}
}
});
}
#Override
public void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
activity.runOnUiThread(new Runnable() {
public void run()
{
}
});
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, int status)
{
Log.d("onCharacteristicread",characteristic.getValue().toString());
Log.d("onCharacteristicread","called");
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
byte[] charValue = characteristic.getValue();
final String str_result = bytesToHex(charValue);
Log.d("onCharacteristicfullres",str_result);
final Intent intent = new Intent("ble_data"); //FILTER is a string to identify this intent
intent.putExtra("val", "getdeviceinfo");
intent.putExtra("data", str_result);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
activity.runOnUiThread(new Runnable() {
public void run()
{
// byte[] charValue = characteristic.getValue();
// String str_result = bytesToHex(charValue);
// Log.d("onCharacteristicfullres",str_result);
//Toast.makeText(activity, "On char changed "+str_result, Toast.LENGTH_SHORT).show();
}
});
}
#Override
public void onDescriptorWrite(final BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status)
{
activity.runOnUiThread(new Runnable() {
public void run()
{
Log.d("oncharadesc","abcd");
// prgd_progress.HideProgressDialog();
BluetoothGattService service = gatt.getService(UUID.fromString(SERVICE_ID));
for (BluetoothGattCharacteristic characteristics: service.getCharacteristics())
{
Log.d("getit",characteristics.getUuid().toString());
}
final BluetoothGattCharacteristic characteristic =
gatt.getService(UUID.fromString(SERVICE_ID)).getCharacteristics().get(0);
byte[] byt_arr;
byt_arr = hexStringToByteArray(value);
characteristic.setValue(byt_arr);
gatt.writeCharacteristic(characteristic);
}
});
}
};
BluetoothGatt bluetoothGatt = bluetoothDevice.connectGatt(activity, true, gattCallback);
}
It's because you call connectGatt multiple times. Every time you call connectGatt you create a GATT client object which listens to notifications. So after three presses you will have three GATT clients that all handles each notification.
You should change the code so that you use the previously created GATT client when you write your data.
Firstly as mentioned in other answers you should not create multiple instances of BluetoothGattCallback just implement those in a way that they exists as single entity for every device for example holding those objects in HashMap or things like that. I also would like to add check if you receive buzy state from ble. It happens on some ble's that they notify twice for single write first response represents buzy state and other gives us the data, well depends on device to device. Thus, kindly perform some checks on ble's behaviour too.
I agree with Emil. Try to establish a connection first, and if the connection is successful, try writing something to the characteristic or descriptor. Also note that in the method writeData() the BluetoothGattCallback is constantly created, it needs to be created only once for each connected device and the caching of the result of the onServicesDiscovered() method so as not to cause it constantly.
Related
I want to get the Heart Rate through BluetoothLe from my Mi Band 2. I tried to follow the example from Getting Started with Bluetooth Low Energy but I didn't succeed to write the value from Heart Rate. I think I miss something but I cannot realize what. It's the first time I am working with bluetooth and smart band. I wish you could help me. I do not know if for starting the Heart Rate sensor should I use as {0x01} byte.
So following the example I downloaded the BluetoothLeDemo app and I got from there BleWrapper BleDefinedUUIDs and BleNamesResolver. I have 2 buttons Scan and stop. I created a BleWrapper, called mBleWrapper to use it for starting scanning and stop scanning.
mBleWrapper = new BleWrapper(this, new BleWrapperUiCallbacks.Null(){
#Override
public void uiDeviceConnected(BluetoothGatt gatt, BluetoothDevice device) {
super.uiDeviceConnected(gatt, device);
}
#Override
public void uiDeviceDisconnected(BluetoothGatt gatt, BluetoothDevice device) {
super.uiDeviceDisconnected(gatt, device);
}
#Override
public void uiAvailableServices(BluetoothGatt gatt, BluetoothDevice device, List<BluetoothGattService> services) {
super.uiAvailableServices(gatt, device, services);
BluetoothGattCharacteristic c=null;
for(BluetoothGattService service : services) {
String serviceName = BleNamesResolver.resolveUuid(service.getUuid().toString());
Log.i("SERVIDE", serviceName);
}
}
c=gatt.getService(BleDefinedUUIDs.Service.HEART_RATE).getCharacteristic(BleDefinedUUIDs.Characteristic.HEART_RATE_MEASUREMENT);
mBleWrapper.writeDataToCharacteristic(c, new byte[]{0x01});
mState = mSensorState.ACC_ENABLE;
}
#Override
public void uiCharacteristicForService(BluetoothGatt gatt, BluetoothDevice device, BluetoothGattService service, List<BluetoothGattCharacteristic> chars) {
super.uiCharacteristicForService(gatt, device, service, chars);
}
#Override
public void uiCharacteristicsDetails(BluetoothGatt gatt, BluetoothDevice device, BluetoothGattService service, BluetoothGattCharacteristic characteristic) {
super.uiCharacteristicsDetails(gatt, device, service, characteristic);
}
#Override
public void uiNewValueForCharacteristic(BluetoothGatt gatt, BluetoothDevice device, BluetoothGattService service, BluetoothGattCharacteristic ch, String strValue, int intValue, byte[] rawValue, String timestamp) {
super.uiNewValueForCharacteristic(gatt, device, service, ch, strValue, intValue, rawValue, timestamp);
switch (mState) {
case ACC_READ:
Log.i("READ", "heart rate dta");
}
Log.i("Value", "Val" + intValue);
}
#Override
public void uiGotNotification(BluetoothGatt gatt, BluetoothDevice device, BluetoothGattService service, BluetoothGattCharacteristic characteristic) {
super.uiGotNotification(gatt, device, service, characteristic);
String ch = BleNamesResolver.resolveCharacteristicName(characteristic.getUuid().toString());
Log.d("AAA", "uiGotNotification: " + ch);
}
#Override
public void uiSuccessfulWrite(BluetoothGatt gatt, BluetoothDevice device, BluetoothGattService service, BluetoothGattCharacteristic ch, String description) {
super.uiSuccessfulWrite(gatt, device, service, ch, description);
BluetoothGattCharacteristic c;
switch (mState) {
case ACC_ENABLE:
Log.i("ENABLED", "Heart Rate enabled");
c = gatt.getService(BleDefinedUUIDs.Service.HEART_RATE).getCharacteristic(BleDefinedUUIDs.Characteristic.HEART_RATE_MEASUREMENT);
mBleWrapper.requestCharacteristicValue(c);
mState = mSensorState.ACC_READ;
break;
case ACC_READ:
Log.i("WRITE", "SUCCESSfule write");
break;
}
}
#Override
public void uiFailedWrite(BluetoothGatt gatt, BluetoothDevice device, BluetoothGattService service, BluetoothGattCharacteristic ch, String description) {
super.uiFailedWrite(gatt, device, service, ch, description);
switch (mState) {
case ACC_ENABLE:
Log.i("FAILED", "Feailed to enbale Heart Rate");
}
}
#Override
public void uiNewRssiAvailable(BluetoothGatt gatt, BluetoothDevice device, int rssi) {
super.uiNewRssiAvailable(gatt, device, rssi);
}
#Override
public void uiDeviceFound(BluetoothDevice device, int rssi, byte[] record) {
super.uiDeviceFound(device, rssi, record);
String msg = "uiDeviceFound: "+device.getAddress()+","+device.getName();
if(devicesList.contains(device)==false) {
Log.i("Devicce", "deviceFound: " + msg);
devicesList.add(device);
genericListAdapter.notifyDataSetChanged();
}
}
});
I display all devices found with a ListView and onClick I want to connect to that item and write the Heart Rate in the Log.
deviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
BluetoothGatt gatt;
BluetoothGattCharacteristic c;
connectDevice((BluetoothDevice) parent.getItemAtPosition(position));
Log.i("Read", "Get Heart Rate");
if(mBleWrapper.isConnected()==false){
Log.i("not connected", "NOT CONNECTE");
return;
}
gatt = mBleWrapper.getGatt();
c = gatt.getService(BleDefinedUUIDs.Service.HEART_RATE).getCharacteristic(BleDefinedUUIDs.Characteristic.HEART_RATE_MEASUREMENT);
mBleWrapper.requestCharacteristicValue(c);
mState=mSensorState.ACC_READ;
}
});
I also added at BleWrapper class onDescpritorWriter
#Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
String deviceName = gatt.getDevice().getName();
String serviceName = BleNamesResolver.resolveServiceName(descriptor.getCharacteristic().getService().getUuid().toString().toLowerCase(Locale.getDefault()));
String charName = BleNamesResolver.resolveCharacteristicName(descriptor.getCharacteristic().getUuid().toString().toLowerCase(Locale.getDefault()));
String description = "Device: " + deviceName + " Service: " + serviceName + " Characteristic: " + charName;
if(status == BluetoothGatt.GATT_SUCCESS) {
mUiCallback.uiSuccessfulWrite(mBluetoothGatt, mBluetoothDevice, mBluetoothSelectedService, descriptor.getCharacteristic(), description);
}
else {
mUiCallback.uiFailedWrite(mBluetoothGatt, mBluetoothDevice, mBluetoothSelectedService, descriptor.getCharacteristic(), description + " STATUS = " + status);
}
}
And for UUIDS for service I used
UUID HEART_RATE = UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb");
and for Charachteristics
UUID HEART_RATE_MEASUREMENT = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb");
In other examples I saw that they also used the UUIDS for descriptor, but me not. I know that is a long post and question, but please I really need some help.
There are something you can check around.
Obviously, you have successfully discovered devices around. According to this(Bluetooth official document) heart rate service (UUID:180d) need set notify to TRUE first. Something like:
Use UUID:00002902-0000-1000-8000-00805f9b34fb to get the descriptor of the characteristic.
BluetoothGattDescriptor descriptor = gattCharacteristic.getDescriptor(Client_Characteristic_Configuration);
Set the feature (Notify) of descriptor to TRUE:
descriptor.setValue((BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE));
Write the value to the BLE device (using Gatt object from the callback function)
gatt.writeDescriptor(descriptor);
After above, you may get the data in override function:
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status)
By this way:
BluetoothGattCharacteristic Char = gatt.getService(FORA_SERVICE_UUID).getCharacteristic(The uuid you want to connect);
byte[] data = Char.getValue();
And maybe you can show the example link. It can be more clear what you had used.
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'm writing printing data to the BluetoothGattCharacteristic of a Zebra ZD410 printer. I do this by chunking the data into 20 byte chunks and writing a chunk at a time with the following code:
mCharacteristic.setValue(bytes);
boolean status = mGatt.writeCharacteristic(mCharacteristic);
and then waiting until I receive BluetoothGattCallback.onCharacteristicWrite() before initiating the writing of the next chunk. This works fine.
If I disconnect() and close() the BluetoothGatt and later connect to the same device again with BluetoothDevice.connectGatt() and then try to write to the Characteristic after onServicesDiscovered() has been called is done and I have my Characteristic again, writing will fail. What I mean by this is that when I write to the Characteristic now, onCharacteristicWrite() will be called with a Characteristic who's getValue() returns the value of the last write on the old Gatt.
After trying to solve this for two days and reading tons of SO posts I haven't found a solution.
How can I fix this?
EDIT
Here is the code for the BluetoothGattCallback
private final BluetoothGattCallback gattCallback = new BluetoothGattCallback()
{
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
{
FALog.i(TAG, "onConnectionStateChange Status: " + status);
switch (newState)
{
case BluetoothProfile.STATE_CONNECTED:
FALog.i(TAG, "gattCallback STATE_CONNECTED");
gatt.discoverServices();
break;
case BluetoothProfile.STATE_DISCONNECTED:
disconnectAndCloseGatt();
mCharacteristic = null;
connectionFailed();
FALog.e(TAG, "gattCallback STATE_DISCONNECTED");
break;
default:
FALog.e(TAG, "gattCallback STATE_OTHER");
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status)
{
BluetoothGattService service = gatt.getService(PRINTER_SERVICE_UUID);
if (service != null)
{
BluetoothGattCharacteristic characteristic = service.getCharacteristic
(PRINTER_SERVICE_CHARACTERISTIC_UUID);
if (characteristic != null)
{
mCharacteristic = characteristic;
mInternalState = STATE_CONNECTED;
mState = State.CONNECTED;
notifyStateChanged();
print("~JA");
FALog.d(TAG, "Printer connected");
mBluetoothActivity.runOnUiThread(new Runnable()
{
#Override
public void run()
{
mListener.onPrinterConnected();
}
});
}
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
{
FALog.d(TAG, "received onCharacteristicWrite " + new String(characteristic.getValue()) + "; success: " +
(status == BluetoothGatt.GATT_SUCCESS));
if (status == BluetoothGatt.GATT_SUCCESS)
{
handler.removeCallbacks(writeRunnable);
popQueueAndReleaseLock();
}
}
};
Try writeCharacteristic after the onDescriptorWrite() callback instead of onServicesDiscovered() callback. writeDescriptor holds the mDeviceBusy.
I am developing an android application which requires me to send data to a Bluetooth Low Energy device.After the connection event is successful and after I receive a call back message I want to change the activity and display new GUI where on switch click I want to send data to connected device. The problem is after the activity has changed my BluetoothGatt becomes null and BluetoothGattCharacteristic also becomes null and i am not able to send the data. how can I solve this issue? Below is my code main class which onResume calls the connection activity and connects to first available device and after connection is successful it receives callback message and changes the activity.
public class MainActivity extends Activity implements BluetoothLeUart.Callback {
public TextView messages;
private BluetoothLeUart uart;
public void writeLine(final CharSequence text) {
runOnUiThread(new Runnable() {
#Override
public void run() {
messages.append(text);
messages.append("\n");
}
});
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
messages = (TextView) findViewById(R.id.messages);
// Initialize UART.
uart = new BluetoothLeUart(getApplicationContext());
messages.setMovementMethod(new ScrollingMovementMethod());
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
protected void onResume() {
super.onResume();
writeLine("Scanning for devices ...");
uart.registerCallback(this);
uart.connectFirstAvailable();
}
#Override
protected void onStop() {
super.onStop();
uart.unregisterCallback(this);
uart.disconnect();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
// UART Callback event handlers.
// UART Callback event handlers.
#Override
public void onConnected(BluetoothLeUart uart) {
// Called when UART device is connected and ready to send/receive data.
//messages.append("connected2");
writeLine("Connected!");
Intent intent = new Intent(MainActivity.this,SwitchClass.class);
startActivity(intent);
}
}
This is my codes BluetoothLeUart class which does connection activity and gives callback message.
public class BluetoothLeUart extends BluetoothGattCallback implements BluetoothAdapter.LeScanCallback {
// UUIDs for UART service and associated characteristics.
public static UUID UART_UUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
public static UUID TX_UUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
public static UUID RX_UUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
// UUID for the UART BTLE client characteristic which is necessary for notifications.
public static UUID CLIENT_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
// UUIDs for the Device Information service and associated characeristics.
public static UUID DIS_UUID = UUID.fromString("0000180a-0000-1000-8000-00805f9b34fb");
public static UUID DIS_MANUF_UUID = UUID.fromString("00002a29-0000-1000-8000-00805f9b34fb");
public static UUID DIS_MODEL_UUID = UUID.fromString("00002a24-0000-1000-8000-00805f9b34fb");
public static UUID DIS_HWREV_UUID = UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb");
public static UUID DIS_SWREV_UUID = UUID.fromString("00002a28-0000-1000-8000-00805f9b34fb");
// Internal UART state.
private Context context;
private WeakHashMap<Callback, Object> callbacks;
private BluetoothAdapter adapter;
private BluetoothGatt gatt;
private BluetoothGattCharacteristic tx;
private BluetoothGattCharacteristic rx;
private boolean connectFirst;
private boolean writeInProgress; // Flag to indicate a write is currently in progress
// Queues for characteristic read (synchronous)
private Queue<BluetoothGattCharacteristic> readQueue;
// Interface for a BluetoothLeUart client to be notified of UART actions.
public interface Callback {
public void onConnected(BluetoothLeUart uart);
public void onConnectFailed(BluetoothLeUart uart);
public void onDisconnected(BluetoothLeUart uart);
public void onReceive(BluetoothLeUart uart, BluetoothGattCharacteristic rx);
public void onDeviceFound(BluetoothDevice device);
public void onDeviceInfoAvailable();
}
public BluetoothLeUart(Context context) {
super();
this.context = context;
this.callbacks = new WeakHashMap<Callback, Object>();
this.adapter = BluetoothAdapter.getDefaultAdapter();
this.gatt = null;
this.tx = null;
this.rx = null;
this.disManuf = null;
this.disModel = null;
this.disHWRev = null;
this.disSWRev = null;
this.disAvailable = false;
this.connectFirst = false;
this.writeInProgress = false;
this.readQueue = new ConcurrentLinkedQueue<BluetoothGattCharacteristic>();
}
// Send data to connected UART device.
public void sendbyte(byte[] data) {
if (tx == null || data == null || data.length == 0) {
// Do nothing if there is no connection or message to send.
return;
}
// Update TX characteristic value. Note the setValue overload that takes a byte array must be used.
tx.setValue(data);
writeInProgress = true; // Set the write in progress flag
gatt.writeCharacteristic(tx);
// ToDo: Update to include a timeout in case this goes into the weeds
while (writeInProgress); // Wait for the flag to clear in onCharacteristicWrite
gatt.readCharacteristic(rx);
}
// Send data to connected UART device.
public void send(String data) {
if (data != null && !data.isEmpty()) {
sendbyte(data.getBytes(Charset.forName("UTF-8")));
}
}
// Register the specified callback to receive UART callbacks.
public void registerCallback(Callback callback) {
callbacks.put(callback, null);
}
// Unregister the specified callback.
public void unregisterCallback(Callback callback) {
callbacks.remove(callback);
}
// Disconnect to a device if currently connected.
public void disconnect() {
if (gatt != null) {
gatt.disconnect();
}
gatt = null;
tx = null;
rx = null;
}
// Stop any in progress UART device scan.
public void stopScan() {
if (adapter != null) {
adapter.stopLeScan(this);
}
}
// Start scanning for BLE UART devices. Registered callback's onDeviceFound method will be called
// when devices are found during scanning.
public void startScan() {
if (adapter != null) {
adapter.startLeScan(this);
}
}
// Connect to the first available UART device.
public void connectFirstAvailable() {
// Disconnect to any connected device.
disconnect();
// Stop any in progress device scan.
stopScan();
// Start scan and connect to first available device.
connectFirst = true;
startScan();
}
// Handlers for BluetoothGatt and LeScan events.
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothGatt.STATE_CONNECTED) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// Connected to device, start discovering services.
if (!gatt.discoverServices()) {
// Error starting service discovery.
connectFailure();
}
else {
notifyOnConnected(this);
}
}
else {
// Error connecting to device.
connectFailure();
}
}
else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
// Disconnected, notify callbacks of disconnection.
rx = null;
tx = null;
notifyOnDisconnected(this);
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
// Notify connection failure if service discovery failed.
if (status == BluetoothGatt.GATT_FAILURE) {
connectFailure();
return;
}
// Save reference to each UART characteristic.
tx = gatt.getService(UART_UUID).getCharacteristic(TX_UUID);
rx = gatt.getService(UART_UUID).getCharacteristic(RX_UUID);
// Save reference to each DIS characteristic.
disManuf = gatt.getService(DIS_UUID).getCharacteristic(DIS_MANUF_UUID);
disModel = gatt.getService(DIS_UUID).getCharacteristic(DIS_MODEL_UUID);
disHWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_HWREV_UUID);
disSWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_SWREV_UUID);
// Add device information characteristics to the read queue
// These need to be queued because we have to wait for the response to the first
// read request before a second one can be processed (which makes you wonder why they
// implemented this with async logic to begin with???)
readQueue.offer(disManuf);
readQueue.offer(disModel);
readQueue.offer(disHWRev);
readQueue.offer(disSWRev);
// Request a dummy read to get the device information queue going
// gatt.readCharacteristic(disManuf);
// Setup notifications on RX characteristic changes (i.e. data received).
// First call setCharacteristicNotification to enable notification.
if (!gatt.setCharacteristicNotification(rx, true)) {
// Stop if the characteristic notification setup failed.
connectFailure();
return;
}
// Next update the RX characteristic's client descriptor to enable notifications.
BluetoothGattDescriptor desc = rx.getDescriptor(CLIENT_UUID);
if (desc == null) {
// Stop if the RX characteristic has no client descriptor.
connectFailure();
return;
}
desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
if (!gatt.writeDescriptor(desc)) {
// Stop if the client descriptor could not be written.
connectFailure();
return;
}
// Notify of connection completion.
notifyOnConnected(this);
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
notifyOnReceive(this, characteristic);
}
#Override
public void onCharacteristicRead (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicRead(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
//Log.w("DIS", characteristic.getStringValue(0));
// Check if there is anything left in the queue
BluetoothGattCharacteristic nextRequest = readQueue.poll();
if(nextRequest != null){
// Send a read request for the next item in the queue
// gatt.readCharacteristic(nextRequest);
}
else {
// We've reached the end of the queue
disAvailable = true;
// notifyOnDeviceInfoAvailable();
}
}
else {
//Log.w("DIS", "Failed reading characteristic " + characteristic.getUuid().toString());
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
// Log.d(TAG,"Characteristic write successful");
}
writeInProgress = false;
}
#Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
// Stop if the device doesn't have the UART service.
if (!parseUUIDs(scanRecord).contains(UART_UUID)) {
//main.writeLine("Parse UUID failed...");
//main.messages.append("Parse UUID failed...");
return;
}
// Connect to first found device if required.
if (connectFirst) {
// Stop scanning for devices.
stopScan();
// Prevent connections to future found devices.
connectFirst = false;
// Connect to device.
gatt = device.connectGatt(context, true, this);
}
}
// Private functions to simplify the notification of all callbacks of a certain event.
private void notifyOnConnected(BluetoothLeUart uart) {
for (Callback cb : callbacks.keySet()) {
if (cb != null) {
cb.onConnected(uart);
}
}
}
private List<UUID> parseUUIDs(final byte[] advertisedData) {
List<UUID> uuids = new ArrayList<UUID>();
int offset = 0;
while (offset < (advertisedData.length - 2)) {
int len = advertisedData[offset++];
if (len == 0)
break;
// main.writeLine(advertisedData.toString());
int type = advertisedData[offset++];
switch (type) {
case 0x02: // Partial list of 16-bit UUIDs
case 0x03: // Complete list of 16-bit UUIDs
//main.writeLine("case 02,03...");
// main.messages.append("case 02,03...");
while (len > 1) {
int uuid16 = advertisedData[offset++];
uuid16 += (advertisedData[offset++] << 8);
len -= 2;
uuids.add(UUID.fromString(String.format("%08x-0000-1000-8000-00805f9b34fb", uuid16)));
}
break;
case 0x06:// Partial list of 128-bit UUIDs
case 0x07:// Complete list of 128-bit UUIDs
// Loop through the advertised 128-bit UUID's.
//main.writeLine("case 06,07...");
// main.messages.append("case 06,07...");
while (len >= 16) {
try {
// Wrap the advertised bits and order them.
ByteBuffer buffer = ByteBuffer.wrap(advertisedData, offset++, 16).order(ByteOrder.LITTLE_ENDIAN);
long mostSignificantBit = buffer.getLong();
long leastSignificantBit = buffer.getLong();
uuids.add(new UUID(leastSignificantBit,
mostSignificantBit));
} catch (IndexOutOfBoundsException e) {
// Defensive programming.
//Log.e(LOG_TAG, e.toString());
continue;
} finally {
// Move the offset to read the next uuid.
offset += 15;
len -= 16;
}
}
break;
default:
//main.writeLine("case default...");
// main.messages.append("case default");
offset += (len - 1);
break;
}
}
return uuids;
}
}
Here is my class from where I send the data
public class SwitchClass extends Activity {
public TextView messages;
public Switch Switch1;
public byte[] switchData = {'U','1','1','1','0','0','2','Z'};
private BluetoothLeUart uart;
public void writeLine(final CharSequence text) {
runOnUiThread(new Runnable() {
#Override
public void run() {
messages.append(text);
messages.append("\n");
//messages.setText("anirudh");
}
});
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.switchlayout);
Switch1 = (Switch) findViewById(R.id.switch1);
uart = new BluetoothLeUart(getApplicationContext());
Switch1.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView,
boolean isChecked) {
if (isChecked) {
switchString = new String(switchData);
writeLine(switchString);
// send this array 8 bytes to BLE
sendData(switchString);
}
}
});
public void sendData(String sendVal) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(sendVal.toCharArray(), 0, 8);
uart.send(stringBuilder.toString());
}
You are creating a service from this activity and you are destroying it onStop
so your service is now been disconnected , so automatically you cant get bluetoothgatt and adapter for it
I suggest you to have child fragment inside you activity so your service will stay alive and you can have as many fragment you want to display the thing !!
Hopes this will help you !!
you can visit https://github.com/captain-miao/bleYan, It's a simple BLE library and example.
I also got exactly the same error like yours. In my case I comment out or deleted the following line under onServiceDiscover method. It works suddenly.It may not be the answer, Hope it will give you some clue to solve.
disManuf = gatt.getService(DIS_UUID).getCharacteristic(DIS_MANUF_UUID);
disModel = gatt.getService(DIS_UUID).getCharacteristic(DIS_MODEL_UUID);
disHWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_HWREV_UUID);
disSWRev = gatt.getService(DIS_UUID).getCharacteristic(DIS_SWREV_UUID);
readQueue.offer(disManuf);
readQueue.offer(disModel);
readQueue.offer(disHWRev);
readQueue.offer(disSWRev);
You must set to synchronized your BluetoothGatt Object like
public void setBluetoothGatt(BluetoothGatt gatt) {
synchronized (this) {
this.bluetoothGatt = gatt;
}
}
because BluetoothGatt throw DeadObject Exception when you change the Activity