Closed Rakesh4a7 closed 2 years ago
You have to create the manager class that will extend BleManager
and handle communication with your device. A simple example you will find here: https://github.com/NordicSemiconductor/Android-nRF-Blinky/blob/master/app/src/main/java/no/nordicsemi/android/blinky/profile/BlinkyManager.java
In nRF Blinky it is indeed used from ViewModel, but it doesn't have to be. I would recommend using it from a service (like here: https://github.com/NordicSemiconductor/Android-nRF-Toolbox/tree/master/app/src/main/java/no/nordicsemi/android/nrftoolbox/csc), but you may just directly use it in your Activity. The example is here: https://github.com/NordicSemiconductor/Android-nRF-Toolbox/tree/master/app/src/main/java/no/nordicsemi/android/nrftoolbox/hrs
Hi @philips77 I tried to follow the example https://github.com/NordicSemiconductor/Android-nRF-Blinky/blob/master/app/src/main/java/no/nordicsemi/android/blinky/profile/BlinkyManager.java
But i'm unable to read the data from my device. This is the demo i made using the classes and changed the UUID. https://github.com/Rakesh4a7/BLEDemo
Can you help me out.
What i want to achieve?
Below is my working code which i want to implement using NRF library.
public void connectToBleDevice(final String deviceId) {
String tempId = "";
for (String s : profile.getBLEDeviceIDs()) {
tempId = s.replace(":", "");
if (tempId.toLowerCase().contentEquals(deviceId)) {
tempId = s;
break;
}
}
if (!tempId.isEmpty()) {
this.deviceId = deviceId;
device = btAdapter.getRemoteDevice(tempId);
stopScanning();
device.connectGatt(mContext, true, mBluetoothGattCallback);
}
}
BluetoothGattCallback mBluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
switch (newState) {
case BluetoothAdapter.STATE_CONNECTED:
gatt.discoverServices();
break;
case BluetoothAdapter.STATE_CONNECTING:
break;
case BluetoothAdapter.STATE_DISCONNECTED:
gatt.disconnect();
// Close the gatt
gatt.close();
startScanning();
break;
case BluetoothAdapter.STATE_DISCONNECTING:
break;
default:
break;
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
BluetoothGattService dataTransferService = gatt.getService(dataTransferServiceUUID);
BluetoothGattCharacteristic responseCharacteristic = dataTransferService.getCharacteristic(responseCharacteristicUUID);
gatt.setCharacteristicNotification(responseCharacteristic, true);
BluetoothGattDescriptor descriptor = responseCharacteristic.getDescriptor(clientCharacteristicConfiguration);
// Enable notification
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
// Write the descriptor to the gatt
gatt.writeDescriptor(descriptor);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
super.onDescriptorWrite(gatt, descriptor, status);
// Now that notification is enabled we can request.
BluetoothGattCharacteristic requestCharacteristic = gatt.getService(dataTransferServiceUUID).getCharacteristic(requestCharacteristicUUID);
// Now set and write the History value
requestCharacteristic.setValue(new byte[]{(byte) 0x03});
gatt.writeCharacteristic(requestCharacteristic);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
java.util.Formatter formatter = new java.util.Formatter();
for (byte b : characteristic.getValue()) {
formatter.format("%02x", b);
}
String hex = formatter.toString();
if (hex.substring(0, 2).contentEquals("83")) {
// Perform operation with data
}
}
};
Hello,
I merged a bit what you have here and there, and here's the initialize()
method, that could work for you:
@Override
protected void initialize() {
// Receive data broadcast by BLE device
setNotificationCallback(responseCharacteristic).with((device, data) -> {
java.util.Formatter formatter = new java.util.Formatter();
for (byte b : data.getValue()) {
formatter.format("%02x", b);
}
String hex = formatter.toString();
if (hex.substring(0, 2).contentEquals("83")) {
// Perform operation with data
}
});
enableNotifications(responseCharacteristic).enqueue();
// Write command to BLE for broadcasting data
writeCharacteristic(requestCharacteristic, new byte[] { (byte) 0x03 }).enqueue();
}
The 0x03 request will be sent automatically with this code. If you want it to be triggered manually, or some time later, just remove the last writeCharacteristic...
line and use your send()
method.
Also, it depends when do you want to get your connect success callback. With the above code you will get it after the request has been written (and the notification enabled before). But you may also want to get it after you receive the notification from the device. In that case, instead of setNotificationCallback(...)
you may use waitForNotification
and write the request as a trigger.
Hi @philips77
I tried the above but request characteristic is always Null. So, unable to call send request.
when isRequiredServiceSupported() method is called?
public class MainActivity extends AppCompatActivity implements BleManagerCallbacks, BlinkyManagerCallbacks {
public final String TAG = "MainActivity";
Context mContext = null;
TextView data;
private final List<BluetoothDevice> mDevices = new ArrayList<>();
/** Flag set to true when scanner is active. */
private boolean mScanning;
private Handler mHandler;
private final static long SCAN_DURATION = 5000;
private boolean mDeviceConnected = false;
private BlinkyManager mBlinkyManager;
private BluetoothDevice mDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
// Initialize the manager
mBlinkyManager = new BlinkyManager(this);
mBlinkyManager.setGattCallbacks(this);
final Button connect = findViewById(R.id.button);
data = findViewById(R.id.data);
mHandler = new Handler();
connect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ensureBLESupported();
if (!isBLEEnabled()) {
showBLEDialog();
return;
}
connectNow();
}
});
}
private void ensureBLESupported() {
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, "BLE not supported", Toast.LENGTH_LONG).show();
finish();
}
}
protected boolean isBLEEnabled() {
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
final BluetoothAdapter adapter = bluetoothManager.getAdapter();
return adapter != null && adapter.isEnabled();
}
protected void showBLEDialog() {
final Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, 1);
}
private void connectNow() {
if (mScanning) {
// Extend scanning for some time more
mHandler.removeCallbacks(mStopScanTask);
mHandler.postDelayed(mStopScanTask, SCAN_DURATION);
return;
}
/* Intent intent = new Intent(this, MyReceiver.class); // explicite intent
intent.setAction("com.example.ACTION_FOUND");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
*/
BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
ScanSettings settings = new ScanSettings.Builder()
// .setLegacy(false)
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
//.setReportDelay(1000)
// .setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED)
.build();
List<ScanFilter> filters = new ArrayList<>();
ScanFilter filter = new ScanFilter.Builder().setDeviceAddress("00:0B:57:47:D5:EA").build();
filters.add(filter);
scanner.startScan(filters, settings, mScanCallback);
// Setup timer that will stop scanning
mHandler.postDelayed(mStopScanTask, SCAN_DURATION);
mScanning = true;
}
public void stopLeScan() {
if (!mScanning)
return;
final BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
scanner.stopScan(mScanCallback);
mHandler.removeCallbacks(mStopScanTask);
mScanning = false;
}
private Runnable mStopScanTask = new Runnable() {
@Override
public void run() {
stopLeScan();
}
};
private ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onScanResult(final int callbackType, final ScanResult result) {
// empty
Log.e("onScanResult","onScanResult : " + result.getDevice().getName() + " " + result.getDevice().getAddress());
BluetoothDevice device = result.getDevice();
if(!mDeviceConnected){
mDevice = device;
reconnect();
data.setText(data.getText() +"\n"+ device.getAddress());
}
}
@Override
public void onBatchScanResults(final List<ScanResult> results) {
final int size = mDevices.size();
BluetoothDevice device;
for (final ScanResult result : results) {
device = result.getDevice();
if(!mDeviceConnected){
mDevice = device;
reconnect();
data.setText(data.getText() +"\n"+ device.getAddress());
}
if (!mDevices.contains(device))
mDevices.add(device);
if (size != mDevices.size()) {
if(!mDeviceConnected){
mDevice = device;
reconnect();
}
data.setText(data.getText() +"\n"+ device.getAddress());
}
}
}
@Override
public void onScanFailed(final int errorCode) {
// empty
}
};
@Override
protected void onPause() {
super.onPause();
stopLeScan();
if(mDeviceConnected)
disconnect();
}
/**
* Reconnects to previously connected device.
* If this device was not supported, its services were cleared on disconnection, so
* reconnection may help.
*/
public void reconnect() {
stopLeScan();
if (mDevice != null) {
mBlinkyManager.connect(mDevice)
.retry(3, 100)
.useAutoConnect(true)
.enqueue();
}
}
/**
* Disconnect from peripheral.
*/
private void disconnect() {
mDevice = null;
mBlinkyManager.disconnect().enqueue();
}
@Override
public void onDeviceConnecting(@NonNull BluetoothDevice device) {
}
@Override
public void onDeviceConnected(@NonNull BluetoothDevice device) {
mDeviceConnected = true;
mBlinkyManager.send();
}
@Override
public void onDeviceDisconnecting(@NonNull BluetoothDevice device) {
}
@Override
public void onDeviceDisconnected(@NonNull BluetoothDevice device) {
mDeviceConnected = false;
connectNow();
}
@Override
public void onLinkLossOccurred(@NonNull BluetoothDevice device) {
mDeviceConnected = false;
}
@Override
public void onServicesDiscovered(@NonNull BluetoothDevice device, boolean optionalServicesFound) {
mBlinkyManager.send();
}
@Override
public void onDeviceReady(@NonNull BluetoothDevice device) {
}
@Override
public void onBondingRequired(@NonNull BluetoothDevice device) {
}
@Override
public void onBonded(@NonNull BluetoothDevice device) {
}
@Override
public void onBondingFailed(@NonNull BluetoothDevice device) {
}
@Override
public void onError(@NonNull BluetoothDevice device, @NonNull String message, int errorCode) {
}
@Override
public void onDeviceNotSupported(@NonNull BluetoothDevice device) {
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && requestCode == 1) {
// Make sure we have access coarse location enabled, if not, prompt the user to enable it
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (mContext.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
if (this.mContext != null) {
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
builder.setTitle("This app needs location access");
builder.setMessage("Please grant location access so this app can detect your bluetooth device.");
builder.setPositiveButton(android.R.string.ok, null);
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
((Activity) mContext).requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 125);
}
}
});
builder.show();
}
} else {// Permission Granted
// Start Scanning
connectNow();
}
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @android.support.annotation.NonNull String[] permissions, @android.support.annotation.NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 125) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
connectNow();
}
}
}
}
public class BlinkyManager extends BleManager<BlinkyManagerCallbacks> {
private BluetoothGattCharacteristic requestCharacteristic;
private BluetoothGattCharacteristic responseCharacteristic;
private boolean mSupported;
// Create the uuid for data transfer service
final UUID dataTransferServiceUUID = UUID.fromString("0b61c398-7697-4762-82d1-5bf490ce0a31");
// Create the uuid's for the request and response characteristics
final UUID requestCharacteristicUUID = UUID.fromString("0b61c399-7697-4762-82d1-5bf490ce0a31");
final UUID responseCharacteristicUUID = UUID.fromString("0b61c39a-7697-4762-82d1-5bf490ce0a31");
// Create UUID for the Client Characteristic Configuration for the response characteristic
final UUID clientCharacteristicConfiguration = UUID.fromString( "00002902-0000-1000-8000-00805F9B34FB");
public BlinkyManager(@NonNull final Context context) {
super(context);
}
@NonNull
@Override
protected BleManagerGattCallback getGattCallback() {
return mGattCallback;
}
@Override
protected boolean shouldClearCacheWhenDisconnected() {
return !mSupported;
}
/**
* BluetoothGatt callbacks object.
*/
private final BleManagerGattCallback mGattCallback = new BleManagerGattCallback() {
@Override
protected void initialize() {
// Receive data broadcast by BLE device
setNotificationCallback(responseCharacteristic).with((device, data) -> {
java.util.Formatter formatter = new java.util.Formatter();
for (byte b : data.getValue()) {
formatter.format("%02x", b);
}
String hex = formatter.toString();
if (hex.substring(0, 2).contentEquals("83")) {
// Perform operation with data
Log.d("data", hex);
}
});
enableNotifications(responseCharacteristic).enqueue();
}
@Override
public boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) {
final BluetoothGattService service = gatt.getService(dataTransferServiceUUID);
if (service != null) {
responseCharacteristic = service.getCharacteristic(responseCharacteristicUUID);
requestCharacteristic = service.getCharacteristic(requestCharacteristicUUID);
}
boolean writeRequest = false;
if (responseCharacteristic != null) {
final int rxProperties = responseCharacteristic.getProperties();
writeRequest = (rxProperties & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0;
}
mSupported = responseCharacteristic != null && writeRequest;
return mSupported;
}
@Override
protected void onDeviceDisconnected() {
responseCharacteristic = null;
requestCharacteristic = null;
}
};
/**
* Sends a request to the device to turn the LED on or off.
*
*/
public void send() {
// Are we connected?
if (requestCharacteristic == null)
return;
// Write command to BLE for broadcasting data
writeCharacteristic(requestCharacteristic, new byte[] { (byte) 0x03 }).enqueue();
}
}
isRequiredServiceSupported(BluetoothGatt)
is called when the device got connected and service discovery has finished.
If your requestCharacteristic
is null, check if it's present in the device and if the UUID you specified is correct. You may use nRF Connect to see what services and characteristics are on the device.
Your code looks OK.
Hi @philips77
I'm using the same characteristic which is working fine with native android code. But not with NRF library.
Do i need to call the send method from onServicesDiscovered() inspite of onDeviceConnected().
No, You may call it from wherever.
Put a breakpoint in isRequiredServiceSupported
and check why the second service you get is null.
Also, in the method of yours;
@Override
public boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) {
final BluetoothGattService service = gatt.getService(dataTransferServiceUUID);
if (service != null) {
responseCharacteristic = service.getCharacteristic(responseCharacteristicUUID);
requestCharacteristic = service.getCharacteristic(requestCharacteristicUUID);
}
boolean writeRequest = false;
if (responseCharacteristic != null) { // <- HERE
final int rxProperties = responseCharacteristic.getProperties();
writeRequest = (rxProperties & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0;
}
mSupported = responseCharacteristic != null && writeRequest;
return mSupported;
}
in the second if
statement, you are checkign the responseCharacteristic
for WRITE property. Is that correct? I guess this one should have rather NOTIFY, and requestCharacteristic
the WRITE one. Or I don't get something?
I'm closing the issue due to inactivity.
Hi,
I'm using Android-Scanner-Compat-Library to scan the data and trying to connect using Android-BLE-Library. I have tried to follow the code provided by Android-nRF-Toolbox. But the problem is it's written using ViewModel pattern and I'm having difficulty to understand and implement in single class.
Can anyone provide an example where the connection part and Read and Write characteristic is written in single class without ViewModel pattern.
Below is the code which i have done till now.