GATT सर्वर से कनेक्ट करना

BLE डिवाइस से इंटरैक्ट करने का पहला चरण, उससे कनेक्ट करना है. खास तौर पर, डिवाइस पर मौजूद GATT सर्वर से कनेक्ट करना. BLE डिवाइस पर मौजूद GATT सर्वर से कनेक्ट करने के लिए, connectGatt() तरीके का इस्तेमाल करें. इस तरीके में तीन पैरामीटर होते हैं: एक Context ऑब्जेक्ट, autoConnect (एक बूलियन जो यह बताता है कि BLE डिवाइस के उपलब्ध होते ही उससे अपने-आप कनेक्ट करना है या नहीं), और एक BluetoothGattCallback का रेफ़रंस:

var bluetoothGatt: BluetoothGatt? = null
// ...
bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)

इससे BLE डिवाइस पर होस्ट किए गए GATT सर्वर से कनेक्शन बनता है. साथ ही, BluetoothGatt इंस्टेंस मिलता है. इसका इस्तेमाल, GATT क्लाइंट के ऑपरेशन करने के लिए किया जा सकता है. कॉल करने वाला (Android ऐप्लिकेशन) GATT क्लाइंट होता है. BluetoothGattCallback का इस्तेमाल, क्लाइंट को नतीजे देने के लिए किया जाता है. जैसे, कनेक्शन की स्थिति और GATT क्लाइंट के अन्य ऑपरेशन.

बाउंड सेवा सेट अप करना

यहां दिए गए उदाहरण में, BLE ऐप्लिकेशन एक गतिविधि (DeviceControlActivity) उपलब्ध कराता है. इसकी मदद से, ब्लूटूथ डिवाइसों से कनेक्ट किया जा सकता है, डिवाइस का डेटा दिखाया जा सकता है, और डिवाइस के साथ काम करने वाली GATT सेवाओं और विशेषताओं को दिखाया जा सकता है. उपयोगकर्ता के इनपुट के आधार पर, यह गतिविधि Service नाम के BluetoothLeService से कम्यूनिकेट करती है. यह BLE API के ज़रिए, BLE डिवाइस से इंटरैक्ट करती है. कम्यूनिकेशन, बाउंड सेवा का इस्तेमाल करके किया जाता है. इससे गतिविधि, BluetoothLeService से कनेक्ट हो पाती है और डिवाइसों से कनेक्ट करने के लिए फ़ंक्शन कॉल कर पाती है. The BluetoothLeService को Binder लागू करने की ज़रूरत होती है. इससे गतिविधि के लिए, सेवा का ऐक्सेस मिलता है.

class BluetoothLeService : Service() {

    private val binder = LocalBinder()

    override fun onBind(intent: Intent): IBinder? {
        return binder
    }

    inner class LocalBinder : Binder() {
        fun getService(): BluetoothLeService {
            return this@BluetoothLeService
        }
    }
}

गतिविधि, bindService() का इस्तेमाल करके सेवा शुरू कर सकती है. इसके लिए, सेवा शुरू करने के लिए Intent, कनेक्शन और डिसकनेक्शन के इवेंट सुनने के लिए ServiceConnection लागू करने की सुविधा, और कनेक्शन के अन्य विकल्प तय करने के लिए फ़्लैग पास करना होता है.

class DeviceControlActivity : AppCompatActivity() {

    private var bluetoothService: BluetoothLeService? = null
    // Code to manage Service lifecycle.
    private val serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(
            componentName: ComponentName,
            service: IBinder
        ) {
            bluetoothService = (service as LocalBinder).getService()
            bluetoothService?.let { bluetooth ->
                // call functions on service to check connection and connect to devices
            }
        }

        override fun onServiceDisconnected(componentName: ComponentName) {
            bluetoothService = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.gatt_services_characteristics)

        val gattServiceIntent = Intent(this, BluetoothLeService::class.java)
        bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE)
    }
}

BluetoothAdapter सेट अप करना

सेवा के बाउंड होने के बाद, उसे BluetoothAdapter का ऐक्सेस चाहिए. उसे यह देखना चाहिए कि डिवाइस पर अडैप्टर उपलब्ध है या नहीं. BluetoothAdapter के बारे में ज़्यादा जानने के लिए, ब्लूटूथ सेट अप करना लेख पढ़ें. यहां दिए गए उदाहरण में, इस सेटअप कोड को initialize() फ़ंक्शन में रैप किया गया है. यह Boolean वैल्यू दिखाता है, जो यह बताती है कि सेटअप पूरा हुआ या नहीं.

private const val TAG = "BluetoothLeService"

class BluetoothLeService : Service() {

    private var bluetoothAdapter: BluetoothAdapter? = null

    fun initialize(): Boolean {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
        if (bluetoothAdapter == null) {
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.")
            return false
        }
        return true
    }

    // ...
}

गतिविधि, इस फ़ंक्शन को ServiceConnection लागू करने के दौरान कॉल करती है. initialize() फ़ंक्शन से मिलने वाली 'गलत' वैल्यू को हैंडल करना, आपके ऐप्लिकेशन पर निर्भर करता है. उपयोगकर्ता को गड़बड़ी का मैसेज दिखाया जा सकता है. इसमें बताया जा सकता है कि मौजूदा डिवाइस, ब्लूटूथ ऑपरेशन के साथ काम नहीं करता. इसके अलावा, उन सुविधाओं को बंद किया जा सकता है जिनके लिए ब्लूटूथ की ज़रूरत होती है. यहां दिए गए उदाहरण में, finish() उपयोगकर्ता को पिछली स्क्रीन पर वापस भेजने के लिए, गतिविधि पर को कॉल किया जाता है.

class DeviceControlActivity : AppCompatActivity() {

    // Code to manage Service lifecycle.
    private val serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(
            componentName: ComponentName,
            service: IBinder
        ) {
            bluetoothService = (service as LocalBinder).getService()
            bluetoothService?.let { bluetooth ->
                if (!bluetooth.initialize()) {
                    Log.e(TAG, "Unable to initialize Bluetooth")
                    finish()
                }
                // perform device connection
            }
        }

        override fun onServiceDisconnected(componentName: ComponentName) {
            bluetoothService = null
        }
    }

    // ...
}

किसी डिवाइस से कनेक्ट करें

BluetoothLeService इंस्टेंस के शुरू होने के बाद, इसे BLE डिवाइस से कनेक्ट किया जा सकता है. गतिविधि को डिवाइस का पता, सेवा को भेजना होगा, ताकि वह कनेक्शन शुरू कर सके. डिवाइस को ऐक्सेस करने के लिए, सेवा सबसे पहले getRemoteDevice() BluetoothAdapter पर कॉल करेगी. अगर अडैप्टर को उस पते वाला कोई डिवाइस नहीं मिलता है, तो getRemoteDevice() एक IllegalArgumentException दिखाता है.

fun connect(address: String): Boolean {
    bluetoothAdapter?.let { adapter ->
        try {
            val device = adapter.getRemoteDevice(address)
        } catch (exception: IllegalArgumentException) {
            Log.w(TAG, "Device not found with provided address.")
            return false
        }
        // connect to the GATT server on the device
        return true
    } ?: run {
        Log.w(TAG, "BluetoothAdapter not initialized")
        return false
    }
}

सेवा के शुरू होने के बाद, DeviceControlActivity, connect() फ़ंक्शन को कॉल करती है. गतिविधि को BLE डिवाइस का पता पास करना होगा. यहां दिए गए उदाहरण में, डिवाइस का पता, गतिविधि को इंटेंट एक्स्ट्रा के तौर पर पास किया जाता है.

// Code to manage Service lifecycle.
private val serviceConnection: ServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(
        componentName: ComponentName,
        service: IBinder
    ) {
        bluetoothService = (service as LocalBinder).getService()
        bluetoothService?.let { bluetooth ->
            if (!bluetooth.initialize()) {
                Log.e(TAG, "Unable to initialize Bluetooth")
                finish()
            }
            // perform device connection
            deviceAddress?.let { bluetooth.connect(it) }
        }
    }

    override fun onServiceDisconnected(componentName: ComponentName) {
        bluetoothService = null
    }
}

GATT कॉलबैक की जानकारी देना

गतिविधि, सेवा को यह बताती है कि किस डिवाइस से कनेक्ट करना है. इसके बाद, सेवा उस डिवाइस से कनेक्ट हो जाती है. अब सेवा को BLE डिवाइस पर मौजूद GATT सर्वर से कनेक्ट करना होगा. इस कनेक्शन के लिए, BluetoothGattCallback की ज़रूरत होती है. इससे कनेक्शन की स्थिति, सेवा की खोज, विशेषता को पढ़ने, और विशेषता की सूचनाओं के बारे में सूचनाएं मिलती हैं.

इस विषय में, कनेक्शन की स्थिति की सूचनाओं पर फ़ोकस किया गया है. सेवा की खोज, विशेषता को पढ़ने, और विशेषता की सूचनाओं का अनुरोध करने के तरीके के बारे में जानने के लिए, BLE डेटा ट्रांसफ़र करना लेख पढ़ें.

डिवाइस के GATT सर्वर से कनेक्शन की स्थिति बदलने पर, onConnectionStateChange() फ़ंक्शन ट्रिगर होता है. यहां दिए गए उदाहरण में, कॉलबैक को Service क्लास में तय किया गया है, ताकि सेवा के BluetoothDevice से कनेक्ट होने के बाद, इसका इस्तेमाल किया जा सके.

private val bluetoothGattCallback = object : BluetoothGattCallback() {
    override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            // successfully connected to the GATT Server
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            // disconnected from the GATT Server
        }
    }
}

GATT सेवा से कनेक्ट करना

BluetoothGattCallback की जानकारी देने के बाद, सेवा, डिवाइस पर मौजूद GATT सेवा से कनेक्ट करने के लिए, connect() फ़ंक्शन से मिले BluetoothDevice ऑब्जेक्ट का इस्तेमाल कर सकती है.

The connectGatt() फ़ंक्शन का इस्तेमाल किया जाता है. इसके लिए, Context ऑब्जेक्ट, autoConnect बूलियन फ़्लैग, और BluetoothGattCallback की ज़रूरत होती है. इस उदाहरण में, ऐप्लिकेशन सीधे BLE डिवाइस से कनेक्ट हो रहा है. इसलिए, autoConnect के लिए false पास किया जाता है.

BluetoothGatt प्रॉपर्टी भी जोड़ी जाती है. इससे सेवा, कनेक्शन की ज़रूरत न होने पर उसे बंद कर सकती है.

class BluetoothLeService : Service() {

    // ...
    private var bluetoothGatt: BluetoothGatt? = null

    // ...
    fun connect(address: String): Boolean {
        bluetoothAdapter?.let { adapter ->
            try {
                val device = adapter.getRemoteDevice(address)
                // connect to the GATT server on the device
                bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback)
                return true
            } catch (exception: IllegalArgumentException) {
                Log.w(TAG, "Device not found with provided address.  Unable to connect.")
                return false
            }
        } ?: run {
            Log.w(TAG, "BluetoothAdapter not initialized")
            return false
        }
    }
}

अपडेट ब्रॉडकास्ट करना

जब सर्वर, GATT सर्वर से कनेक्ट या डिसकनेक्ट होता है, तो उसे गतिविधि को नई स्थिति के बारे में सूचना देनी होती है. ऐसा करने के कई तरीके हैं. यहां दिए गए उदाहरण में, सेवा से गतिविधि तक जानकारी भेजने के लिए ब्रॉडकास्ट का इस्तेमाल किया गया है.

सेवा, नई स्थिति को ब्रॉडकास्ट करने के लिए एक फ़ंक्शन तय करती है. इस फ़ंक्शन में एक ऐक्शन स्ट्रिंग होती है. इसे सिस्टम पर ब्रॉडकास्ट करने से पहले, Intent ऑब्जेक्ट को पास किया जाता है.

private fun broadcastUpdate(action: String) {
    val intent = Intent(action)
    sendBroadcast(intent)
}

ब्रॉडकास्ट फ़ंक्शन सेट अप होने के बाद, इसका इस्तेमाल BluetoothGattCallback में किया जाता है. इससे GATT सर्वर के साथ कनेक्शन की स्थिति के बारे में जानकारी भेजी जाती है. सेवा में, कॉन्स्टैंट और सेवा की मौजूदा कनेक्शन की स्थिति तय की जाती है. ये Intent ऐक्शन को दिखाते हैं.

class BluetoothLeService : Service() {

    private var connectionState = STATE_DISCONNECTED

    private val bluetoothGattCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                // successfully connected to the GATT Server
                connectionState = STATE_CONNECTED
                broadcastUpdate(ACTION_GATT_CONNECTED)
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                // disconnected from the GATT Server
                connectionState = STATE_DISCONNECTED
                broadcastUpdate(ACTION_GATT_DISCONNECTED)
            }
        }
    }

    // ...
    companion object {
        const val ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED"
        const val ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"

        private const val STATE_DISCONNECTED = 0
        private const val STATE_CONNECTED = 2
    }
}

गतिविधि में अपडेट सुनना

सेवा के कनेक्शन के अपडेट ब्रॉडकास्ट करने के बाद, गतिविधि को लागू करना होगा BroadcastReceiver. गतिविधि सेट अप करते समय, इस रिसीवर को रजिस्टर करें. साथ ही, स्क्रीन से बाहर जाते समय इसे अनरजिस्टर करें. सेवा से मिलने वाले इवेंट सुनकर, गतिविधि, BLE डिवाइस के साथ मौजूदा कनेक्शन की स्थिति के आधार पर, उपयोगकर्ता इंटरफ़ेस को अपडेट कर पाती है.

class DeviceControlActivity : AppCompatActivity() {

    private val gattUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            when (intent.action) {
                BluetoothLeService.ACTION_GATT_CONNECTED -> {
                    connected = true
                    updateConnectionState(R.string.connected)
                }
                BluetoothLeService.ACTION_GATT_DISCONNECTED -> {
                    connected = false
                    updateConnectionState(R.string.disconnected)
                }
            }
        }
    }

    override fun onResume() {
        super.onResume()
        registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter())
        bluetoothService?.let { service ->
            deviceAddress?.let { address ->
                val result = service.connect(address)
                Log.d(TAG, "Connect request result=$result")
            }
        }
    }

    override fun onPause() {
        super.onPause()
        unregisterReceiver(gattUpdateReceiver)
    }

    private fun makeGattUpdateIntentFilter(): IntentFilter {
        return IntentFilter().apply {
            addAction(BluetoothLeService.ACTION_GATT_CONNECTED)
            addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED)
        }
    }
}

BLE डेटा ट्रांसफ़र करना लेख में, का इस्तेमाल, सेवा की खोज के साथ-साथ डिवाइस से मिलने वाले विशेषता के डेटा के बारे में बताने के लिए भी किया जाता है.BroadcastReceiver

GATT कनेक्शन बंद करना

ब्लूटूथ कनेक्शन के साथ काम करते समय, एक अहम चरण यह है कि काम पूरा होने के बाद कनेक्शन बंद कर दिया जाए. ऐसा करने के लिए, BluetoothGatt ऑब्जेक्ट पर close() फ़ंक्शन को कॉल करें. यहां दिए गए उदाहरण में, सेवा, BluetoothGatt का रेफ़रंस रखती है. जब गतिविधि, सेवा से अनबाउंड होती है, तो डिवाइस की बैटरी खत्म होने से बचाने के लिए कनेक्शन बंद कर दिया जाता है.

class BluetoothLeService : Service() {

    // ...
    override fun onUnbind(intent: Intent?): Boolean {
        close()
        return super.onUnbind(intent)
    }

    private fun close() {
        bluetoothGatt?.let { gatt ->
            gatt.close()
            bluetoothGatt = null
        }
    }
}