i would to to show the value of heart rate from Polar H7 on TextView in Fragment (not on the Activity). I used Google's example from the address: http://developer.android.com/guide/topics/connectivity/bluetooth-le.html I have made BluetoothLeService class, and SampleGattAttributes help class. I'm posting the code of all three class. But, in the end my TextView does not change. Can you tell me where the break is. Thanks.
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public final static String ACTION_GATT_CONNECTED =
"com.schleewoon.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"com.schleewoon.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.schleewoon.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"com.schleewoon.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.schleewoon.bluetooth.le.EXTRA_DATA";
public final static UUID UUID_HEART_RATE_MEASUREMENT =
UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);
private final IBinder mBinder = new LocalBinder();
// Various callback methods defined by the BLE API.
private final BluetoothGattCallback mGattCallback =
new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i(TAG, "Connected to GATT server.");
Log.i(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}
#Override
// New services discovered
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
#Override
// Result of a characteristic read operation
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
};
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
// This is special handling for the Heart Rate Measurement profile. Data
// parsing is carried out as per profile specifications.
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
int flag = characteristic.getProperties();
int format = -1;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.d(TAG, "Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG, "Heart rate format UINT8.");
}
final int heartRate = characteristic.getIntValue(format, 1);
Log.d(TAG, String.format("Received heart rate: %d", heartRate));
intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
} else {
// For all other profiles, writes the data formatted in HEX.
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data)
stringBuilder.append(String.format("%02X ", byteChar));
intent.putExtra(EXTRA_DATA, new String(data) + "\n" +
stringBuilder.toString());
}
}
sendBroadcast(intent);
}
public class LocalBinder extends Binder {
public BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/**
* Initializes a reference to the local Bluetooth adapter.
*
* #return Return true if the initialization is successful.
*/
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public boolean initialize() {
// For API level 18 and above, get a reference to BluetoothAdapter
// through
// BluetoothManager.
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* #param address
* The device address of the destination device.
*
* #return Return true if the connection is initiated successfully. The
* connection result is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG,
"BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null
&& address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.d(TAG,
"Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter
.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the
// autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
/**
* Disconnects an existing connection or cancel a pending connection. The
* disconnection result is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public void disconnect() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.disconnect();
}
/**
* After using a given BLE device, the app must call this method to ensure
* resources are released properly.
*/
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
/**
* Request a read on a given {#code BluetoothGattCharacteristic}. The read
* result is reported asynchronously through the
* {#code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
* callback.
*
* #param characteristic
* The characteristic to read from.
*/
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
/**
* Enables or disables notification on a give characteristic.
*
* #param characteristic
* Characteristic to act on.
* #param enabled
* If true, enable notification. False otherwise.
*/
public void setCharacteristicNotification(
BluetoothGattCharacteristic characteristic, boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
try {
// This is specific to Heart Rate Measurement.
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
BluetoothGattDescriptor descriptor = characteristic
.getDescriptor(UUID
.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor
.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
} catch (Exception e) {
Log.d(TAG,
"Exception while setting up notification for heartrate.", e);
}
}
/**
* Retrieves a list of supported GATT services on the connected device. This
* should be invoked only after {#code BluetoothGatt#discoverServices()}
* completes successfully.
*
* #return A {#code List} of supported services.
*/
public List<BluetoothGattService> getSupportedGattServices() {
if (mBluetoothGatt == null)
return null;
return mBluetoothGatt.getServices();
}
}
public class SampleGattAttributes {
private static HashMap<String, String> attributes = new HashMap<String, String>();
public static String HEART_RATE_MEASUREMENT = "00002a37-0000-1000-8000-00805f9b34fb";
public static String CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
static {
// Sample Services.
attributes.put("00001800-0000-1000-8000-00805f9b34fb", "Generic access");
attributes.put("00001801-0000-1000-8000-00805f9b34fb", "Generic attribute");
attributes.put("00001802-0000-1000-8000-00805f9b34fb", "Immediate alert");
attributes.put("00001803-0000-1000-8000-00805f9b34fb", "Link loss");
attributes.put("00001804-0000-1000-8000-00805f9b34fb", "Tx Power");
attributes.put("00001805-0000-1000-8000-00805f9b34fb", "Current Time Service");
attributes.put("0000180d-0000-1000-8000-00805f9b34fb", "Heart Rate Service");
attributes.put("0000180a-0000-1000-8000-00805f9b34fb", "Device Information Service");
// Sample Characteristics.
attributes.put(HEART_RATE_MEASUREMENT, "Heart Rate Measurement");
attributes.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
attributes.put("00002a00-0000-1000-8000-00805f9b34fb", "Device Name");
attributes.put("00002a01-0000-1000-8000-00805f9b34fb", "Appearance");
attributes.put("00002a02-0000-1000-8000-00805f9b34fb", "Peripheral Privacy Flag");
attributes.put("00002a03-0000-1000-8000-00805f9b34fb", "Reconnection Address");
attributes.put("00002a04-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
attributes.put("00002a05-0000-1000-8000-00805f9b34fb", "Service Changed");
attributes.put("00002A06-0000-1000-8000-00805f9b34fb", "Alert level");
attributes.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
attributes.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
attributes.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
attributes.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
attributes.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
}
public static String lookup(String uuid, String defaultName) {
String name = attributes.get(uuid);
return name == null ? defaultName : name;
}
public class CO2Fragment extends Fragment {
private TextView totalTimeTitle;
private BluetoothLeService mBluetoothLeService;
private final static String TAG = CO2Fragment.class.getSimpleName();
private static final int BIND_AUTO_CREATE = 0x0001;
private String mDeviceAddress;
private boolean mConnected = false;
// Code to manage Service lifecycle.
private final ServiceConnection mServiceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName,
IBinder service) {
mBluetoothLeService = ((BluetoothLeService.LocalBinder) service)
.getService();
if (!mBluetoothLeService.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
getActivity().finish();
}
// Automatically connects to the device upon successful start-up
// initialization.
mBluetoothLeService.connect(mDeviceAddress);
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
mBluetoothLeService = null;
}
};
// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a
// result of read
// or notification operations.
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
mConnected = true;
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED
.equals(action)) {
mConnected = false;
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED
.equals(action)) {
// Show all the supported services and characteristics on the
// user interface.
//displayGattServices(mBluetoothLeService.getSupportedGattServices());
// mButtonStop.setVisibility(View.VISIBLE);
} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
displayData(intent
.getStringExtra(BluetoothLeService.EXTRA_DATA));
}
}
};
private static IntentFilter makeGattUpdateIntentFilter() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
intentFilter
.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
return intentFilter;
}
#Override
public View onCreateView(LayoutInflater inflater,
#Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
// TODO Auto-generated method stub
...
totalTimeTitle = (TextView) co2view.findViewById(R.id.textViewCO2TotalTimeTitle);
Intent gattServiceIntent = new Intent(getActivity(), BluetoothLeService.class);
// TODO: Lars added this
getActivity().startService(gattServiceIntent);
getActivity().bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
getActivity().registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
...
return co2view;
}
private void displayData(String data) {
if (data != null) {
totalTimeTitle.setText("TOTAL TIME + CURRENT PULSE: " + data);
}
else{
totalTimeTitle.setText("TOTAL TIME ---" );
}
}
The line:
int flag = characteristic.getProperties();
in the sample code is expected to return the bit mask for the supported characteristic fields. It doesn't. It returns the characteristic properties, like PROPERTY_NOTIFY.
To get the bit mask for the supported characteristic fields, in the case of the example's Heart Rate Measurement characteristic, the first value of the characteristic needs to be read and used.
Thus replacing the line
int flag = characteristic.getProperties();
with something like
byte[] charValue = characteristic.getValue();
byte flag = charValue[0];
will work correctly.
Related
I have an app that is using BLE connectivity with my device. Everything works from connecting to the device, discovering the services, writing the characteristic and descriptor. But for some reason, onCharacteristicChanged is not triggering. The goal is to retrieve the data from the characteristic that is in the characteristic. I have tried using a different characteristic from the same service and this works as in retrieving the data. Not sure why this specific characteristic is not working when others are.
Here is my code:
//Descriptor
public final static UUID UUID_CLIENT_CHARACTERISTIC_CONFIG = UUID.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG);
// Huneo Service
public final static UUID UUID_HUNEO_SERVICE = UUID.fromString(GattAttributes.HUNEO_SERVICE);
public final static UUID UUID_HUNEO_VIBRATOR = UUID.fromString(GattAttributes.HUNEO_VIBRATOR);
public final static UUID UUID_HUNEO_MONITOR = UUID.fromString(GattAttributes.HUNEO_MONITOR);
public final static UUID UUID_HUNEO_RATES = UUID.fromString(GattAttributes.HUNEO_RATES);
public final static UUID UUID_HUNEO_LED = UUID.fromString(GattAttributes.HUNEO_LED);
public final static UUID UUID_HUNEO_ACCEL = UUID.fromString(GattAttributes.HUNEO_ACCEL);
public final static UUID UUID_HUNEO_GYRO = UUID.fromString(GattAttributes.HUNEO_GYRO);
public final static UUID UUID_HUNEO_COMBINED = UUID.fromString(GattAttributes.HUNEO_COMBINED);
// Battery Service
public final static UUID UUID_BATTERY_SERVICE = UUID.fromString(GattAttributes.BATTERY_SERVICE);
public final static UUID UUID_BATTERY_LEVEL = UUID.fromString(GattAttributes.BATTERY_LEVEL);
// Device Info Service
public final static UUID UUID_DEVICE_INFO_SERVICE = UUID.fromString(GattAttributes.DEVICE_INFO_SERVICE);
public final static UUID UUID_MODEL_NUMBER = UUID.fromString(GattAttributes.MODEL_NUMBER);
public final static UUID UUID_SERIAL_NUMBER = UUID.fromString(GattAttributes.SERIAL_NUMBER);
public final static UUID UUID_FIRMWARE_REV = UUID.fromString(GattAttributes.FIRMWARE_REV);
public final static UUID UUID_HARDWARE_REV = UUID.fromString(GattAttributes.HARDWARE_REV);
public final static UUID UUID_MANUFACTURER_NAME = UUID.fromString(GattAttributes.MANUFACTURER_NAME);
private final static String TAG = "UNITY";
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
private final BluetoothDevice mDevice;
private final IBinder mBinder = new LocalBinder();
private TaskCompletionSource<Void> mConnectionTask = new TaskCompletionSource<>();
private TaskCompletionSource<String> mFirmwareRevTask = new TaskCompletionSource<>();
private TaskCompletionSource<Void> mServicesDiscovered = new TaskCompletionSource<>();
private TaskCompletionSource<Integer> mBatteryLevelTask = new TaskCompletionSource<>();
private TaskCompletionSource<SensorValues> mStartStreamingTask = new TaskCompletionSource<>();
private TaskCompletionSource<String> mSerialNumberTask = new TaskCompletionSource<>();
private SensorValues mSensorValues = new SensorValues();
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private boolean mIsStreaming = false;
// 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) {
mConnectionState = STATE_CONNECTED;
Log.i(TAG, "Connected to GATT server.");
// Attempts to discover services after successful connection.
Log.i(TAG, "Attempting to start service discovery:" + mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
mConnectionState = STATE_DISCONNECTED;
mIsStreaming = false;
Log.i(TAG, "Disconnected from GATT server.");
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
mServicesDiscovered.setResult(null);
startStreaming();
Log.w(TAG, "onServicesDiscovered success.");
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
String characteristicUuid = characteristic.getUuid().toString();
if (characteristicUuid.equals(GattAttributes.FIRMWARE_REV)) {
String firmwareRev = characteristic.getStringValue(0);
mFirmwareRevTask.setResult(firmwareRev);
}
if (characteristicUuid.equals(GattAttributes.BATTERY_LEVEL)) {
int batteryLevel = characteristic.getIntValue(FORMAT_UINT8, 0);
mBatteryLevelTask.setResult(batteryLevel);
}
if (characteristicUuid.equals(GattAttributes.SERIAL_NUMBER)) {
String serialNumber = characteristic.getStringValue(0);
mSerialNumberTask.setResult(serialNumber);
}
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
if (characteristic.getUuid().equals(UUID_HUNEO_MONITOR)) {
if (characteristic.getIntValue(FORMAT_UINT8, 0) == 1) {
startCombined();
} else {
stopCombined();
}
}
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
String characteristicUuid = characteristic.getUuid().toString();
//Combined value changed
if (characteristicUuid.equals(GattAttributes.HUNEO_COMBINED)) {
byte[] data = characteristic.getValue();
int id = unsignedShortToInt(data[0], data[1]);
int x = unsignedShortToInt(data[2], data[3]);
int y = unsignedShortToInt(data[4], data[5]);
int z = unsignedShortToInt(data[6], data[7]);
mSensorValues.combined = new CartesianValues(id,x,y,z);
mStartStreamingTask.setResult(mSensorValues);
}
}
#Override
public void onDescriptorWrite(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
//Combined
if (descriptor.getCharacteristic().getUuid().equals(UUID_HUNEO_COMBINED)) {
mIsStreaming = Arrays.equals(descriptor.getValue(), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mStartStreamingTask.setResult(null);
}
}
}
};
public AlwaysOnHuneoBoard(BluetoothDevice device) {
this.mDevice = device;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
#Override
public boolean onUnbind(Intent intent) {
// After using a given device, you should make sure that BluetoothGatt.close() is called
// such that resources are cleaned up properly. In this particular example, close() is
// invoked when the UI is disconnected from the Service.
close();
return super.onUnbind(intent);
}
public static class LocalBinder extends Binder {
}
/**
* Connect to the device
*
* #param ct CancellationToken that will make task return if cancelled
* #return when task is complete the device is connected
*/
public Task<Void> connectAsync(CancellationToken ct) {
if (ct.isCancellationRequested()) {
Log.w(TAG, "Connection cancelled");
mConnectionTask.setCancelled();
return mConnectionTask.getTask();
}
if (mDevice == null) {
Log.w(TAG, "BluetoothDevice not initialized");
mConnectionTask.setError(new Exception("BluetoothDevice not initialized"));
return mConnectionTask.getTask();
}
// Previously connected device. BluetoothGatt not closed. Try to reconnect.
if (mBluetoothGatt != null) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
mConnectionTask.setResult(null);
return mConnectionTask.getTask();
}
}
mBluetoothGatt = mDevice.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mConnectionState = STATE_CONNECTING;
mConnectionTask.setResult(null);
return mConnectionTask.getTask();
}
/**
* Disconnects an existing connection or cancel a pending connection. The disconnection result
* is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public void disconnect() {
if (mDevice == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothDevice not initialized");
return;
}
mBluetoothGatt.disconnect();
}
/**
* After using a given BLE device, the app must call this method to ensure resources are
* released properly.
*/
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
/**
* Determines if connected to the BLE device.
*
* #return true if connected, false otherwise.
*/
public boolean isConnected() {
return mConnectionState == STATE_CONNECTED;
}
/**
* Get the calibration for the sensor
*
* #return integer representation of the calibration
*/
public int getCalibration() {
return 100;
}
/**
* Get the firmware version of the sensor
*
* #return string representing the devices firmware version
*/
public String getFirmwareVersion() {
try {
// Wait for services to be discovered
mServicesDiscovered.getTask().onSuccessTask(t -> {
BluetoothGattService deviceInfoService = mBluetoothGatt.getService(UUID_DEVICE_INFO_SERVICE);
BluetoothGattCharacteristic characteristic = deviceInfoService.getCharacteristic(UUID_FIRMWARE_REV);
mBluetoothGatt.readCharacteristic(characteristic);
mFirmwareRevTask.setResult(null);
return mFirmwareRevTask.getTask();
// Wait for deviceRevTask to be completed
}).waitForCompletion(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return "";
}
return mFirmwareRevTask.getTask().getResult();
}
/**
* Get the device mac address
*
* #return string representation of the mac address
*/
public String getMacAddress() {
return mDevice.getAddress();
}
/**
* Get the model of the device
*
* #return string representation of the device model
*/
public String getModel() {
return mDevice.getName();
}
public SensorValues getCombinedSensorValues() {
if (!isConnected()) {
Log.i("Unity", "Sensor disconnected, using default sensor values.");
return mSensorValues;
}
if (!mIsStreaming) {
Log.i("Unity", "Sensor not streaming, using default sensor values.");
return mSensorValues;
}
try {
mStartStreamingTask.getTask().waitForCompletion();
} catch (InterruptedException ex) {
ex.printStackTrace();
disconnect();
}
return mSensorValues;
}
/**
* Read the current battery level from the device
*
* #return Task containing the sensors battery level as an integer
*/
public Task<Integer> getBatteryLevelAsync() {
// Wait for services to be discovered
return mServicesDiscovered.getTask().onSuccessTask(t -> {
BluetoothGattService deviceInfoService = mBluetoothGatt.getService(UUID_BATTERY_SERVICE);
BluetoothGattCharacteristic characteristic = deviceInfoService.getCharacteristic(UUID_BATTERY_LEVEL);
mBluetoothGatt.readCharacteristic(characteristic);
return mBatteryLevelTask.getTask();
});
}
public Task<String> getSerialNumber() {
return mServicesDiscovered.getTask().onSuccessTask(t -> {
BluetoothGattService deviceInfoService = mBluetoothGatt.getService(UUID_DEVICE_INFO_SERVICE);
BluetoothGattCharacteristic characteristic = deviceInfoService.getCharacteristic(UUID_SERIAL_NUMBER);
mBluetoothGatt.readCharacteristic(characteristic);
return mSerialNumberTask.getTask();
});
}
/**
* Issue command to sensor to begin streaming combined data
*/
public void startStreaming() {
if (mIsStreaming) {
return;
}
//Enable notifications for combined, set result
//Write 0 to Monitor Characteristic
BluetoothGattService huneoService = mBluetoothGatt.getService(UUID_HUNEO_SERVICE);
BluetoothGattCharacteristic monitorCharacteristic = huneoService.getCharacteristic(UUID_HUNEO_MONITOR);
monitorCharacteristic.setValue(1, FORMAT_UINT8, 0);
mBluetoothGatt.writeCharacteristic(monitorCharacteristic);
}
/**
* Issue command to sensor to stop streaming accel and gyro data
*/
public void stopStreaming() {
if (!mIsStreaming) {
return;
}
//Disable notifications for combined
BluetoothGattService huneoService = mBluetoothGatt.getService(UUID_HUNEO_SERVICE);
BluetoothGattCharacteristic monitorCharacteristic = huneoService.getCharacteristic(UUID_HUNEO_MONITOR);
monitorCharacteristic.setValue(0, FORMAT_UINT8, 0);
mBluetoothGatt.writeCharacteristic(monitorCharacteristic);
}
/**
* Trigger vibration haptics on the sensor
*/
public void triggerHaptics() {
mServicesDiscovered.getTask().onSuccessTask(t -> {
BluetoothGattService huneoService = mBluetoothGatt.getService(UUID_HUNEO_SERVICE);
// Set vibration characteristic to 1
BluetoothGattCharacteristic characteristic = huneoService.getCharacteristic(UUID_HUNEO_VIBRATOR);
characteristic.setValue(0, FORMAT_UINT8, 0);
mBluetoothGatt.writeCharacteristic(characteristic);
return t;
});
}
//Start Combined Notifications
private void startCombined() {
mServicesDiscovered.getTask().onSuccessTask(t -> {
BluetoothGattService huneoService = mBluetoothGatt.getService(UUID_HUNEO_SERVICE);
// Set Combined characteristic to send notifications
BluetoothGattCharacteristic combinedCharacteristic = huneoService.getCharacteristic(UUID_HUNEO_COMBINED);
mBluetoothGatt.setCharacteristicNotification(combinedCharacteristic, true);
//Set Combined Client Characteristic Config Descriptor to enable notifications
BluetoothGattDescriptor combinedDescriptor = combinedCharacteristic.getDescriptor(UUID.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
combinedDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(combinedDescriptor);
return t;
});
}
//Stop Combined notifications
private void stopCombined() {
mServicesDiscovered.getTask().onSuccessTask(t -> {
BluetoothGattService huneoService = mBluetoothGatt.getService(UUID_HUNEO_SERVICE);
// Set Accelerometer characteristic to send notifications
BluetoothGattCharacteristic combinedCharacteristic = huneoService.getCharacteristic(UUID_HUNEO_COMBINED);
mBluetoothGatt.setCharacteristicNotification(combinedCharacteristic, false);
// Set Accelerometer Client Characteristic Config Descriptor to enable notifications
BluetoothGattDescriptor combinedDescriptor = combinedCharacteristic.getDescriptor(UUID.fromString(GattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
combinedDescriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(combinedDescriptor);
return t;
});
}
// Convert two bytes into a short
private int unsignedShortToInt(byte firstByte, byte secondByte) {
ByteBuffer bb = ByteBuffer.allocate(2);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.put(firstByte);
bb.put(secondByte);
short shortVal = bb.getShort(0);
return shortVal >= 0 ? shortVal : 0x10000 + shortVal;
}
}
Hopefully, someone can help me out. I'm out of ideas.
Found the solution. The third-party forgot to mention that I had to set the right value to enable the characteristic. I tried that and it worked! Finally, got data values.
I want to get every value of the array messageBytes in onCharacteristicChanged method but the logcat message is a bit messy. I have made my values in bold style. Any solution to get every single array value? Thanks.
05-05 17:57:15.763 14107-14119/com.example.android.bluetoothlegatt E/BluetoothLeService: Puck:[J[ 18, 30, 2.86,
05-05 17:57:15.800 14107-14119/com.example.android.bluetoothlegatt I/BluetoothLeService: onCharacteristicChanged
05-05 17:57:15.800 14107-14119/com.example.android.bluetoothlegatt E/BluetoothLeService: Puck:
"normal"
]
package com.example.android.bluetoothlegatt;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;
/**
* Service for managing connection and data communication with a GATT server hosted on a
* given Bluetooth LE device.
*/
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public final static String ACTION_GATT_CONNECTED =
"com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.example.bluetooth.le.EXTRA_DATA";
// Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered.
private final BluetoothGattCallback mGattCallback;
{
mGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i(TAG, "Connected to GATT server.");
// Attempts to discover services after successful connection.
Log.i(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
setPuckCharacteristicNotification();
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "onCharacteristicRead");
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
Log.i(TAG, "onCharacteristicChanged");
byte[] messageBytes = characteristic.getValue();
String messageString = null;
try {
messageString = new String(messageBytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Unable to convert message bytes to string");
}
Log.e(TAG, "Puck:" + messageString);
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
Log.i(TAG, "onCharacteristicWrite");
}
#Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
Log.i(TAG, "onDescriptorWrite");
BluetoothGattService service = gatt.getService(G.PUCK_SERVICE_UUID);
if (service != null) {
BluetoothGattCharacteristic characteristic =
service.getCharacteristic(G.PUCK_TX_UUID);
if (characteristic != null) {
Log.i(TAG, "Writing to characteristic " + characteristic.getUuid());
//String msg = "E.getTemperature()\n";
//byte[] data = msg.getBytes();
//characteristic.setValue(data);
if (gatt.writeCharacteristic(characteristic)) {
Log.i(TAG, "writeCharacteristic OK");
} else {
Log.i(TAG, "writeCharacteristic FAIL");
}
}
}
}
};
}
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
// This is special handling for the Heart Rate Measurement profile. Data parsing is
// carried out as per profile specifications:
// http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
if (G.PUCK_RX_UUID.equals(characteristic.getUuid())) {
int flag = characteristic.getProperties();
int format = -1;
/*if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.i(TAG, "Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.i(TAG, "Heart rate format UINT8.");
}*/
//final int heartRate = characteristic.getIntValue(format, 1);
//Log.i(TAG, String.format("Received heart rate: %d", heartRate));
//intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
} else {
// For all other profiles, writes the data formatted in HEX.
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for (byte byteChar : data)
stringBuilder.append(String.format("%02X ", byteChar));
intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
}
}
sendBroadcast(intent);
}
public class LocalBinder extends Binder {
BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
#Override
public boolean onUnbind(Intent intent) {
// After using a given device, you should make sure that BluetoothGatt.close() is called
// such that resources are cleaned up properly. In this particular example, close() is
// invoked when the UI is disconnected from the Service.
close();
return super.onUnbind(intent);
}
private final IBinder mBinder = new LocalBinder();
/**
* Initializes a reference to the local Bluetooth adapter.
*
* #return Return true if the initialization is successful.
*/
public boolean initialize() {
// For API level 18 and above, get a reference to BluetoothAdapter through
// BluetoothManager.
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* #param address The device address of the destination device.
* #return Return true if the connection is initiated successfully. The connection result
* is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.i(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.i(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
/**
* Disconnects an existing connection or cancel a pending connection. The disconnection result
* is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public void disconnect() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.disconnect();
}
/**
* After using a given BLE device, the app must call this method to ensure resources are
* released properly.
*/
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
/**
* Request a read on a given {#code BluetoothGattCharacteristic}. The read result is reported
* asynchronously through the {#code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
* callback.
*
* #param characteristic The characteristic to read from.
*/
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
/**
* Enables notification on a given characteristic.
**/
public void setPuckCharacteristicNotification() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
//Start getting data
BluetoothGattService gattService = mBluetoothGatt.getService(G.PUCK_SERVICE_UUID);
if (gattService != null) {
BluetoothGattCharacteristic gattCharacteristic = gattService.getCharacteristic(G.PUCK_RX_UUID);
if (gattCharacteristic != null) {
Log.i(TAG, "Setting Characteristic " + gattCharacteristic.getUuid());
mBluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
BluetoothGattDescriptor clientDescriptor = gattCharacteristic.getDescriptor(G.CLIENT_CHARACTERISTIC_CONFIG);
clientDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(clientDescriptor);
}
}
}
/**
* Retrieves a list of supported GATT services on the connected device. This should be
* invoked only after {#code BluetoothGatt#discoverServices()} completes successfully.
*
* #return A {#code List} of supported services.
*/
public List<BluetoothGattService> getSupportedGattServices() {
if (mBluetoothGatt == null) return null;
return mBluetoothGatt.getServices();
}
public int getElement(byte[] arrayOfBytes, int index) {
return arrayOfBytes[index];
}
}
I am trying to write a characteristic value on a BLE peripheral. I am using the code mentioned below(writeCharacteristics method). I am able to write the characteristics on the peripheral on a button click, but this operation is successful only once. Subsequent write are not sent to the device as I can verify this from checking BLE logs over the air. Also the callback onCharacteristicWrite is not fired when I use the write characteristic API.
Here since I set the property in the peripheral, I am not performing any check.
Code:
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
private BluetoothGatt mBluetoothGatt;
private int mConnectionState = STATE_DISCONNECTED;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public final static String ACTION_GATT_CONNECTED =
"com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.example.bluetooth.le.EXTRA_DATA";
public final static UUID UUID_HEART_RATE_MEASUREMENT =
UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);
// 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) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i(TAG, "Connected to GATT server.");
// Attempts to discover services after successful connection.
Log.i(TAG, "Attempting to start service discovery:" +
mBluetoothGatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
Log.v("Callback", "Entered the callabck onCharacteristics");
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
public void writeCharacteristics(byte[] data_write) {
UUID uuid_service = UUID.fromString(SampleGattAttributes.MY_SERVICE_UUID);
UUID uuid_char = UUID.fromString(SampleGattAttributes.MY_CHAR_UUID);
BluetoothGattService mSVC = mBluetoothGatt.getService(uuid_service);
BluetoothGattCharacteristic mCH = mSVC.getCharacteristic(uuid_char);
mCH.setValue(data_write);
mCH.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
mBluetoothGatt.writeCharacteristic(mCH);
}
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
// This is special handling for the Heart Rate Measurement profile. Data parsing is
// carried out as per profile specifications:
// http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
int flag = characteristic.getProperties();
int format = -1;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.d(TAG, "Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG, "Heart rate format UINT8.");
}
final int heartRate = characteristic.getIntValue(format, 1);
Log.d(TAG, String.format("Received heart rate: %d", heartRate));
intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
} else {
// For all other profiles, writes the data formatted in HEX.
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data)
stringBuilder.append(String.format("%02X ", byteChar));
intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
}
}
sendBroadcast(intent);
}
public class LocalBinder extends Binder {
BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
#Override
public boolean onUnbind(Intent intent) {
// After using a given device, you should make sure that BluetoothGatt.close() is called
// such that resources are cleaned up properly. In this particular example, close() is
// invoked when the UI is disconnected from the Service.
close();
return super.onUnbind(intent);
}
private final IBinder mBinder = new LocalBinder();
/**
* Initializes a reference to the local Bluetooth adapter.
*
* #return Return true if the initialization is successful.
*/
public boolean initialize() {
// For API level 18 and above, get a reference to BluetoothAdapter through
// BluetoothManager.
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* #param address The device address of the destination device.
*
* #return Return true if the connection is initiated successfully. The connection result
* is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
/**
* Disconnects an existing connection or cancel a pending connection. The disconnection result
* is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public void disconnect() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.disconnect();
}
/**
* After using a given BLE device, the app must call this method to ensure resources are
* released properly.
*/
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
/**
* Request a read on a given {#code BluetoothGattCharacteristic}. The read result is reported
* asynchronously through the {#code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
* callback.
*
* #param characteristic The characteristic to read from.
*/
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
/**
* Enables or disables notification on a give characteristic.
*
* #param characteristic Characteristic to act on.
* #param enabled If true, enable notification. False otherwise.
*/
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
// This is specific to Heart Rate Measurement.
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
}
/**
* Retrieves a list of supported GATT services on the connected device. This should be
* invoked only after {#code BluetoothGatt#discoverServices()} completes successfully.
*
* #return A {#code List} of supported services.
*/
public List<BluetoothGattService> getSupportedGattServices() {
if (mBluetoothGatt == null) return null;
return mBluetoothGatt.getServices();
}
}
I am using Android N and One Plus 3T hardware. Any comments on this is greatly appreciated.
What is the result of method call BluetoothGatt::writeCharacteristic(BluetoothGattCharacteristic characteristic)
According to the doc
it returns boolean true, if the write operation was initiated successfully
It will return false if you try to do more than one write or read in a row. You have to wait for the result of a previous read or write before doing the next one.
This is probably why it works only the first time.
hi everyone i am working on android app which requires connecting to ble device and sending data over ble. i have tried scanning and displaying the scanned devices on a list which works fine. the problem is with connecting to the scanned device. here is the part of my main activity code.
public class main_activity extends Activity implements BluetoothLeUart.Callback{
public ImageButton fabbutton;
Activity activity;
private ArrayAdapter<String> adapter;
private ArrayList<String> liste,devicedata;
private ListView list;
public EditText input;
String name,address,Devicename,Deviceaddress,datadevicename;
private BluetoothGatt mBluetoothGatt;
public static String SelectedDeviceName;
private BluetoothAdapter mBluetoothAdapter;
public ArrayList<BluetoothDevice> dev;
private Handler mHandler;
private static final int REQUEST_ENABLE_BT = 1;
private static final long SCAN_PERIOD = 10000;
BluetoothLeUart uart = new BluetoothLeUart(this);
public static BluetoothDevice device;
private BluetoothGatt mGatt;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_activity);
fabbutton = (ImageButton) findViewById(R.id.fabbutton);
activity = this;
mHandler = new Handler();
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
scanLeDevice(true);
mActionBar.setTitle(Html.fromHtml("<font color='#727272'>Board List</font>"));
list = (ListView) findViewById(R.id.list);
liste = new ArrayList<String>();
liste.clear();
adapter = new ArrayAdapter<String>(list.getContext(), android.R.layout.simple_list_item_1, liste);
list.setAdapter(adapter);
// On Click Listener for Paired BLE Device List
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
SelectedDeviceName = list.getItemAtPosition(position).toString();
String[] splitString = SelectedDeviceName.split(" ");
Devicename = splitString[0];
Deviceaddress = splitString[1];
device = mBluetoothAdapter.getRemoteDevice(Deviceaddress);
Toast.makeText(getApplicationContext(),device.toString(),Toast.LENGTH_SHORT).show();
//device.connectGatt(getApplicationContext(), true, gattCallback);
uart.connectFirstAvailable();// this is where i start connecting process.
}
});
here is the code of my BluetoothLeUart class
public class BluetoothLeUart extends BluetoothGattCallback implements BluetoothAdapter.LeScanCallback {
public String mDeviceAddress;
// 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
// Device Information state.
private BluetoothGattCharacteristic disManuf;
private BluetoothGattCharacteristic disModel;
private BluetoothGattCharacteristic disHWRev;
private BluetoothGattCharacteristic disSWRev;
private boolean disAvailable;
// 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>();
}
// Return instance of BluetoothGatt.
public BluetoothGatt getGatt() {
return gatt;
}
// Return true if connected to UART device, false otherwise.
public boolean isConnected() {
return (tx != null && rx != null);
}
public String getDeviceInfo() {
if (tx == null || !disAvailable ) {
// Do nothing if there is no connection.
return "";
}
StringBuilder sb = new StringBuilder();
sb.append("Manufacturer : " + disManuf.getStringValue(0) + "\n");
sb.append("Model : " + disModel.getStringValue(0) + "\n");
sb.append("Firmware : " + disSWRev.getStringValue(0) + "\n");
return sb.toString();
};
public boolean deviceInfoAvailable() { return disAvailable; }
// Send data to connected UART device.
public void send(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
}
public void registerCallback(Callback callback) {
callbacks.put(callback, null);
}
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);
Toast.makeText(context,"Entered",Toast.LENGTH_SHORT).show();
}
}
// 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 {
// 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);
// 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) {
Toast.makeText(context, "onLe Entered", Toast.LENGTH_SHORT).show();
if (!parseUUIDs(scanRecord).contains(UART_UUID)) {
return;
}
// Notify registered callbacks of found device.
notifyOnDeviceFound(device);
// Connect to first found device if required.
if (connectFirst) {
// Stop scanning for devices.
// Prevent connections to future found devices.
if (mDeviceAddress.equals(device.getAddress())) {
stopScan();
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 void notifyOnConnectFailed(BluetoothLeUart uart) {
for (Callback cb : callbacks.keySet()) {
if (cb != null) {
cb.onConnectFailed(uart);
}
}
}
private void notifyOnDisconnected(BluetoothLeUart uart) {
for (Callback cb : callbacks.keySet()) {
if (cb != null) {
cb.onDisconnected(uart);
}
}
}
private void notifyOnReceive(BluetoothLeUart uart, BluetoothGattCharacteristic rx) {
for (Callback cb : callbacks.keySet()) {
if (cb != null ) {
cb.onReceive(uart, rx);
}
}
}
private void notifyOnDeviceFound(BluetoothDevice device) {
for (Callback cb : callbacks.keySet()) {
if (cb != null) {
cb.onDeviceFound(device);
}
}
}
private void notifyOnDeviceInfoAvailable() {
for (Callback cb : callbacks.keySet()) {
if (cb != null) {
cb.onDeviceInfoAvailable();
}
}
}
// Notify callbacks of connection failure, and reset connection state.
private void connectFailure() {
rx = null;
tx = null;
notifyOnConnectFailed(this);
}
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;
int type = advertisedData[offset++];
switch (type) {
case 0x02: // Partial list of 16-bit UUIDs
case 0x03: // Complete list of 16-bit UUIDs
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.
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:
offset += (len - 1);
break;
}
}
return uuids;
}
}
here is the code of my BluetoothLeService class
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private String mBluetoothDeviceAddress;
public BluetoothGatt mBluetoothGatt;
private int mConnectionState =STATE_DISCONNECTED;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public final static String ACTION_GATT_CONNECTED =
"com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.example.bluetooth.le.EXTRA_DATA";
// 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");
// Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered.
public final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.i("STATE CONNECTED", "OK");
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(intentAction);
Log.i("STATE CONNECTED BROADCAST SENT - DISCOVERING SERVICES", "OK");
// Attempts to discover services after successful connection.
mBluetoothGatt.discoverServices();
Log.i("AFTER DISCOVER SERVICES", "OK");
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.i("STATE DISCONNECTED", "OK");
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(intentAction);
Log.i("STATE DISCONNECTED BROADCAST SENT", "OK");
}
if(mConnectionState==123334){
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
Log.i("GATT SUCCESS - SERVICES DISCOVERED", "OK");
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
Log.i("INTENT SENT", "OK");
}
private void broadcastUpdate(final String action, final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
// For all other profiles, writes the data formatted in HEX.
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for(byte byteChar : data)
stringBuilder.append(String.format("%02X ", byteChar));
intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
}
sendBroadcast(intent);
}
public class LocalBinder extends Binder {
public BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
#Override
public boolean onUnbind(Intent intent) {
close();
return super.onUnbind(intent);
}
private final IBinder mBinder = new LocalBinder();
public boolean initialize() {
// For API level 18 and above, get a reference to BluetoothAdapter through
// BluetoothManager.
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect.
if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
mBluetoothDeviceAddress = address;
mConnectionState = STATE_CONNECTING;
return true;
}
public void disconnect() {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.disconnect();
}
public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enabled) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
}
public List<BluetoothGattService> getSupportedGattServices() {
if (mBluetoothGatt == null) return null;
return mBluetoothGatt.getServices();
}
}
What is the target version of targetSdkVersion. I had similar issue and changed build.gradle to
defaultConfig {
applicationId "com.XXX.XXXr"
minSdkVersion 21
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
It solved the problem, but I have some others needs to be solved...
In my case (Adafruit Feather BLE) the following worked
public static String LOG_TAG = "Adafruit Ind";
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");
public static UUID CLIENT_UUID = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB");
public static UUID DIS_UUID = UUID.fromString("000001530-1212-EFDE-1523-785FEABCD123");
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");
I have a problem trying to connect with 2 Bluetooth LE modules (RN4020) throught native Android Bluetooth LE API and based on the Bluetooth Gatt sample of Android developers (https://developer.android.com/samples/BluetoothLeGatt/index.html). I modified BluetoothLeService.java to hold many connections instead of only one.
This is my process:
1- Start a new service which handle connections
2- Loop to connect devices:
for(String address: addresses) {
mBluetoothLeService.connect(address)
}
3- Find characteristics I need
4- Send data:
mBluetoothLeService.writeCharacteristic(address, mCharacteristic, dataArray);
The thing is that when I send data to the first module, it receive it perfect (onCharacteristicWrite() in BluetoothGattCallback() is called), then I send data to the second module and everything OK! But when I try to send data to the first module again, it doesn't receive it (tested in the board and also onCharacteristicWrite() is not called).
I also try with Reliable Write:
btGatt.beginReliableWrite();
characteristic.setValue(bytearray);
btGatt.writeCharacteristic(characteristic);
btGatt.executeReliableWrite();
but the same problem. The callback:
onReliableWriteCompleted(BluetoothGatt gatt, int status)
Status code is 6 (BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED)
I checked connection status to both modules and there were connected every time (no disconnection problem).
Here is the modified class BluetoothLeService.java:
public class BluetoothLeService extends Service {
private final static String TAG = BluetoothLeService.class.getSimpleName();
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
// private String mBluetoothDeviceAddress;
private Map<String,BluetoothGatt> mBluetoothGatts = new HashMap<>();
private int mConnectionState = STATE_DISCONNECTED;
private static final int STATE_DISCONNECTED = 0;
private static final int STATE_CONNECTING = 1;
private static final int STATE_CONNECTED = 2;
public final static String ACTION_GATT_CONNECTED =
"com.example.bluetooth.le.ACTION_GATT_CONNECTED";
public final static String ACTION_GATT_DISCONNECTED =
"com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
public final static String ACTION_DATA_AVAILABLE =
"com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
public final static String EXTRA_DATA =
"com.example.bluetooth.le.EXTRA_DATA";
public final static String EXTRA_ADDRESS =
"com.example.bluetooth.le.EXTRA_ADDRESS";
public final static UUID UUID_HEART_RATE_MEASUREMENT =
UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);
// 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) {
String intentAction;
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;
broadcastUpdate(gatt.getDevice().getAddress(), intentAction);
Log.i(TAG, "Connected to GATT server. address: " + gatt.getDevice().getAddress());
// Attempts to discover services after successful connection.
Log.i(TAG, "Attempting to start service discovery:" +
gatt.discoverServices());
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
broadcastUpdate(gatt.getDevice().getAddress(), intentAction);
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
Log.i(TAG, "--onServicesDiscovered. status: " + status);
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(gatt.getDevice().getAddress(), ACTION_GATT_SERVICES_DISCOVERED);
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
}
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(gatt.getDevice().getAddress(), ACTION_DATA_AVAILABLE, characteristic);
}
}
#Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
Log.i(TAG, "--onCharacteristicWrite: value: " + Arrays.toString(characteristic.getValue()));
}
#Override
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
super.onReliableWriteCompleted(gatt, status);
Log.i(TAG, "--onReliableWriteCompleted: status: " + status);
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
broadcastUpdate(gatt.getDevice().getAddress(), ACTION_DATA_AVAILABLE, characteristic);
}
};
private void broadcastUpdate(String address, final String action) {
final Intent intent = new Intent(action);
intent.putExtra(EXTRA_ADDRESS, address);
sendBroadcast(intent);
}
private void broadcastUpdate(final String address, final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
// This is special handling for the Heart Rate Measurement profile. Data parsing is
// carried out as per profile specifications:
// http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
int flag = characteristic.getProperties();
int format = -1;
if ((flag & 0x01) != 0) {
format = BluetoothGattCharacteristic.FORMAT_UINT16;
Log.d(TAG, "Heart rate format UINT16.");
} else {
format = BluetoothGattCharacteristic.FORMAT_UINT8;
Log.d(TAG, "Heart rate format UINT8.");
}
final int heartRate = characteristic.getIntValue(format, 1);
Log.d(TAG, String.format("Received heart rate: %d", heartRate));
intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
} else {
// For all other profiles, writes the data formatted in HEX.
final byte[] data = characteristic.getValue();
if (data != null && data.length > 0) {
final StringBuilder stringBuilder = new StringBuilder(data.length);
for (byte byteChar : data)
stringBuilder.append(String.format("%02X ", byteChar));
intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
}
}
intent.putExtra(EXTRA_ADDRESS, address);
sendBroadcast(intent);
}
public class LocalBinder extends Binder {
public BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
#Override
public boolean onUnbind(Intent intent) {
// After using a given device, you should make sure that BluetoothGatt.close() is called
// such that resources are cleaned up properly. In this particular example, close() is
// invoked when the UI is disconnected from the Service.
disconnectAll();
closeAll();
return super.onUnbind(intent);
}
private final IBinder mBinder = new LocalBinder();
/**
* Initializes a reference to the local Bluetooth adapter.
*
* #return Return true if the initialization is successful.
*/
public boolean initialize() {
// For API level 18 and above, get a reference to BluetoothAdapter through
// BluetoothManager.
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
/**
* Connects to the GATT server hosted on the Bluetooth LE device.
*
* #param address The device address of the destination device.
* #return Return true if the connection is initiated successfully. The connection result
* is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
BluetoothGatt btGatt = mBluetoothGatts.get(address);
// Previously connected device. Try to reconnect.
if ( btGatt != null) {
Log.d(TAG, "---Trying to use an existing mBluetoothGatt for connection.");
if (btGatt.connect()) {
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "---Device not found. Unable to connect.");
return false;
}
// We want to directly connect to the device, so we are setting the autoConnect
// parameter to false.
BluetoothGatt newBtGatt = device.connectGatt(this, true, mGattCallback);
Log.d(TAG, "---Trying to create a new connection. address: " + address);
mBluetoothGatts.put(address, newBtGatt);
mConnectionState = STATE_CONNECTING;
return true;
}
/**
* Disconnects an existing connection or cancel a pending connection. The disconnection result
* is reported asynchronously through the
* {#code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* callback.
*/
public void disconnect(String address) {
BluetoothGatt btGatt = mBluetoothGatts.get(address);
if (mBluetoothAdapter == null || btGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
Log.i(TAG, "disconnecting gatt");
btGatt.disconnect();
}
public void disconnectAll() {
// Connecting all devices
Iterator it = mBluetoothGatts.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
String address = (String) pair.getKey();
disconnect(address);
}
}
public int getConnectionState() {
return mConnectionState;
}
/**
* After using a given BLE device, the app must call this method to ensure resources are
* released properly.
*/
public void close(String address, boolean removeFromMap) {
BluetoothGatt btGatt = mBluetoothGatts.get(address);
if (btGatt == null) {
return;
}
Log.i(TAG, "closing gatt");
btGatt.close();
if (removeFromMap) {
mBluetoothGatts.remove(address);
}
}
public void closeAll() {
// Connecting all devices
Iterator it = mBluetoothGatts.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry) it.next();
String address = (String) pair.getKey();
close(address, false);
}
}
/**
* Request a read on a given {#code BluetoothGattCharacteristic}. The read result is reported
* asynchronously through the {#code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
* callback.
*
* #param characteristic The characteristic to read from.
*/
public void readCharacteristic(String address, BluetoothGattCharacteristic characteristic) {
BluetoothGatt btGatt = mBluetoothGatts.get(address);
if (mBluetoothAdapter == null || btGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
btGatt.readCharacteristic(characteristic);
}
public void writeCharacteristic(String address, BluetoothGattCharacteristic characteristic,
byte[] bytearray) {
BluetoothGatt btGatt = mBluetoothGatts.get(address);
if (mBluetoothAdapter == null || btGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
if (characteristic == null) {
Log.w(TAG, "Characteristic == null");
}
Log.i(TAG, "--> Writing characacteristic: " + Arrays.toString(bytearray) + ", device address: " + address);
// btGatt.beginReliableWrite();
characteristic.setValue(bytearray);
// BluetoothManager bluetoothManager =
// (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
// Log.i(TAG, "--connection state: " + bluetoothManager.getConnectionState(btGatt.getDevice(), BluetoothGatt.GATT_SERVER));
btGatt.writeCharacteristic(characteristic);
// btGatt.executeReliableWrite();
}
/**
* Enables or disables notification on a give characteristic.
*
* #param characteristic Characteristic to act on.
* #param enabled If true, enable notification. False otherwise.
*/
public void setCharacteristicNotification(String address, BluetoothGattCharacteristic characteristic,
boolean enabled) {
BluetoothGatt btGatt = mBluetoothGatts.get(address);
if (mBluetoothAdapter == null || btGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
btGatt.setCharacteristicNotification(characteristic, enabled);
// This is specific to Heart Rate Measurement.
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
btGatt.writeDescriptor(descriptor);
}
}
/**
* Retrieves a list of supported GATT services on the connected device. This should be
* invoked only after {#code BluetoothGatt#discoverServices()} completes successfully.
*
* #return A {#code List} of supported services.
*/
public List<BluetoothGattService> getSupportedGattServices(String address) {
BluetoothGatt btGatt = mBluetoothGatts.get(address);
if (btGatt == null) return null;
return btGatt.getServices();
}
}
Any ideas or libraries to handle multiple BLE connections? Thanks!