Connect to BLE device

Viewed 1452

So I've made this app, where I find all BLE Devices with a name. But how can I make one of the specific fields, clickable and automatic connect to the device, so I can start writing/reading from it?

Adapter

public class ListAdapter_BTLE_Devices extends ArrayAdapter<BTLE_Device> {

    Activity activity;
    int layoutResourceID;
    ArrayList<BTLE_Device> devices;

    public ListAdapter_BTLE_Devices(Activity activity, int resource, ArrayList<BTLE_Device> objects) {
        super(activity.getApplicationContext(), resource, objects);
        this.activity = activity;
        layoutResourceID = resource;
        devices = objects;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if (convertView == null) {
            LayoutInflater inflater =
                    (LayoutInflater) activity.getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            convertView = inflater.inflate(layoutResourceID, parent, false);
        }

        BTLE_Device device = devices.get(position);
        String name = device.getName();
        String address = device.getAddress();
        int rssi = device.getRSSI();

        TextView BLE_name = (TextView) convertView.findViewById(R.id.BLE_name);
        if (name != null && name.length() > 0) {
            BLE_name.setText(device.getName());
        }
        else {
            BLE_name.setText("No Name");
        }

        TextView BLE_rssi = (TextView) convertView.findViewById(R.id.BLE_rssi);
        BLE_rssi.setText("RSSI: " + Integer.toString(rssi));

        TextView BLE_macaddr = (TextView) convertView.findViewById(R.id.BLE_macaddr);
        if (address != null && address.length() > 0) {
            BLE_macaddr.setText("MAC-addr: "+device.getAddress());
        }
        else {
            BLE_macaddr.setText("No Address");
        }

        return convertView;
    }
}

EDIT

I think i might be connected to the GATT now, so what I've done is.. To start with i get the MAC-addr from the Mainactivity and then I saved it in a intent, and started another activity onclick. Here I did the follwing DeviceAddress = intent.getStringExtra(MainActivity.EXTRAS_BLE_ADDRESS);

BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(DeviceAddress);

device.connectGatt(this, false, mGattCallback);

and when I call connectGatt it prints the message Log.d(TAG, "Connection State: 1");, is this the right way to do it?


private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            Log.d(TAG, "Connection State Change: "+status+" -> "+connectionState(newState));
            if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
                /*
                 * Once successfully connected, we must next discover all the services on the
                 * device before we can read and write their characteristics.
                 */
                Log.d(TAG, "Connection State: 1");
                gatt.discoverServices();
            } else if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_DISCONNECTED) {
                /*
                 * If at any point we disconnect, send a message to clear the weather values
                 * out of the UI
                 */
                Log.d(TAG, "Connection State: 2");
            } else if (status != BluetoothGatt.GATT_SUCCESS) {
                /*
                 * If there is a failure at any stage, simply disconnect
                 */
                Log.d(TAG, "Connection State: 3");
                gatt.disconnect();
            }
        }
3 Answers

If you have problems with Bluetooth LE I suggest you to use my bluetooth le library (don't reinvent the wheel, it tooks me about 3/4 months to make the library, a bluetooth le communication can be really tricky to make), it is open source so you can also see the code for having an example of implementation, I link you the github page: https://github.com/niedev/BluetoothCommunicator

For use the library in a project you have to add jitpack.io to your root build.gradle (project):

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Then add the last version of BluetoothCommunicator to your app build.gradle

dependencies {
        implementation 'com.github.niedev:BluetoothCommunicator:1.0.6'
}

To use this library add these permissions to your manifest:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

Then add android:largeHeap="true" to the application tag in the manifest:
Example

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

<application
    android:name="com.bluetooth.communicatorexample.Global"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:largeHeap="true"
    android:theme="@style/Theme.Speech">
    <activity android:name="com.bluetooth.communicatorexample.MainActivity"
        android:configChanges="orientation|screenSize">

        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

Once you have downloaded the libray and set the manifest, you need to create a bluetooth communicator object, it is the object that handles all operations of bluetooth low energy library, if you want to manage the bluetooth connections in multiple activities I suggest you to save this object as an attribute of a custom class that extends Application and create a getter so you can access to bluetoothCommunicator from any activity or service with:

((custom class name) getApplication()).getBluetoothCommunicator();

Next step is to initialize bluetoothCommunicator, the parameters are: a context, the name by which the other devices will see us (limited to 18 characters and can be only characters listed in BluetoothTools.getSupportedUTFCharacters(context) because the number of bytes for advertising beacon is limited) and the strategy (for now the only supported stategy is BluetoothCommunicator.STRATEGY_P2P_WITH_RECONNECTION)

bluetoothCommunicator = new BluetoothCommunicator(this, "device name", BluetoothCommunicator.STRATEGY_P2P_WITH_RECONNECTION);

Then add the bluetooth communicator callback, the callback will listen for all events of bluetooth communicator:

bluetoothCommunicator.addCallback(new BluetoothCommunicator.Callback() {
    @Override
    public void onBluetoothLeNotSupported() {
        super.onBluetoothLeNotSupported();
        
        Notify that bluetooth low energy is not compatible with this device
    }
   
    @Override
    public void onAdvertiseStarted() {
        super.onAdvertiseStarted();

        Notify that advertise has started, if you want to do something after the start of advertising do it here, because
        after startAdvertise there is no guarantee that advertise is really started (it is delayed)
    }

    @Override
    public void onDiscoveryStarted() {
        super.onDiscoveryStarted();

        Notify that discovery has started, if you want to do something after the start of discovery do it here, because
        after startDiscovery there is no guarantee that discovery is really started (it is delayed)
    }

    @Override
    public void onAdvertiseStopped() {
        super.onAdvertiseStopped();

        Notify that advertise has stopped, if you want to do something after the stop of advertising do it here, because
        after stopAdvertising there is no guarantee that advertise is really stopped (it is delayed)
    }

    @Override
    public void onDiscoveryStopped() {
        super.onDiscoveryStopped();

        Notify that discovery has stopped, if you want to do something after the stop of discovery do it here, because
        after stopDiscovery there is no guarantee that discovery is really stopped (it is delayed)
    }

    @Override
    public void onPeerFound(Peer peer) {
        super.onPeerFound(peer);
        
        Here for example you can save peer in a list or anywhere you want and when the user
        choose a peer you can call bluetoothCommunicator.connect(peer founded) but if you want to
        use a peer for connect you have to have peer updated (see onPeerUpdated or onPeerLost), if you use a
        non updated peer the connection might fail
        instead if you want to immediate connect where peer is found you can call bluetoothCommunicator.connect(peer) here
    }

    @Override
    public void onPeerLost(Peer peer){
        super.onPeerLost(peer);
        
        It means that a peer is out of range or has interrupted the advertise,
        here you can delete the peer lost from a eventual collection of founded peers
    }

    @Override
    public void onPeerUpdated(Peer peer,Peer newPeer){
        super.onPeerUpdated(peer,newPeer);

        It means that a founded peer (or connected peer) has changed (name or address or other things),
        if you have a collection of founded peers, you need to replace peer with newPeer if you want to connect successfully to that peer.

        In case the peer updated is connected and you have saved connected peers you have to update the peer if you want to successfully
        send a message or a disconnection request to that peer.
    }

    @Override
    public void onConnectionRequest(Peer peer){
        super.onConnectionRequest(peer);

        It means you have received a connection request from another device (peer) (that have called connect)
        for accept the connection request and start connection call bluetoothCommunicator.acceptConnection(peer);
        for refusing call bluetoothCommunicator.rejectConnection(peer); (the peer must be the peer argument of onConnectionRequest)
    }

    @Override
    public void onConnectionSuccess(Peer peer,int source){
        super.onConnectionSuccess(peer,source);

        This means that you have accepted the connection request using acceptConnection or the other
        device has accepted your connection request and the connection is complete, from now on you
        can send messages or data (or disconnection request) to this peer until onDisconnected

        To send messages to all connected peers you need to create a message with a context, a header, represented by a single character string
        (you can use a header to distinguish between different types of messages, or you can ignore it and use a random
        character), the text of the message, or a series of bytes if you want to send any kind of data and the peer you want to send the message to
        (must be connected to avoid errors), example: new Message(context,"a","hello world",peer);
        If you want to send message to a specific peer you have to set the sender of the message with the corresponding peer.

        To send disconnection request to connected peer you need to call bluetoothCommunicator.disconnect(peer);
    }

    @Override
    public void onConnectionFailed(Peer peer,int errorCode){
        super.onConnectionFailed(peer,errorCode);

        This means that your connection request is rejected or has other problems,
        to know the cause of the failure see errorCode (BluetoothCommunicator.CONNECTION_REJECTED
        means rejected connection and BluetoothCommunicator.ERROR means generic error)
    }

    @Override
    public void onConnectionLost(Peer peer){
        super.onConnectionLost(peer);

        This means that a connected peer has lost the connection with you and the library is trying
        to restore it, in this case you can update the gui to notify this problem.

        You can still send messages in this situation, all sent messages are put in a queue
        and sent as soon as the connection is restored
    }

    @Override
    public void onConnectionResumed(Peer peer){
        super.onConnectionResumed(peer);
        
        Means that connection lost is resumed successfully
    }

    @Override
    public void onMessageReceived(Message message,int source){
        super.onMessageReceived(message,source);

        Means that you have received a message containing TEXT, for know the sender you can call message.getSender() that return
        the peer that have sent the message, you can ignore source, it indicate only if you have received the message
        as client or as server
    }

    @Override
    public void onDataReceived(Message data,int source){
        super.onDataReceived(data,source);

        Means that you have received a message containing DATA, for know the sender you can call message.getSender() that return
        the peer that have sent the message, you can ignore source, it indicate only if you have received the message
        as client or as server
    }

    @Override
    public void onDisconnected(Peer peer,int peersLeft){
        super.onDisconnected(peer,peersLeft);

        Means that the peer is disconnected, peersLeft indicate the number of connected peers remained
    }

    @Override
    public void onDisconnectionFailed(){
        super.onDisconnectionFailed();

        Means that a disconnection is failed, super.onDisconnectionFailed will reactivate bluetooth for forcing disconnection
        (however the disconnection will be notified in onDisconnection)
    }
});

Finally you can start discovery and/or advertising:

bluetoothCommunicator.startAdvertising();
bluetoothCommunicator.startDiscovery();

All other actions that can be done are explained with the comments in the code of callback I wrote before.

To connect to the Device first you must perform you BLE scan which (if your using the starter code) runs a callback and add it to a list of found devices.

Add a filter in to only allow the set device you are looking for. As BLE advertises a packet upto 31 bytes you should have some data in here which discerns you device such as manufacturer id or data etc. Or if you are working on a simple project you can programmatically hard code in the device address.

Then when this device is discovered from the scan you can stop your BLE scan and automatically queue a connection request. This will ask for the GATT request to be made and therefore, grant you access to the GATT services and thus characteristics on the device.

You can add a view to your holder and set a click listener to it. A view could be a transparent rectangle all around your display card (or whatever you use).

I'd suggest this in depth read regarding BLE usage. On the click listener you can queue up the connection request.

Related