GATT সার্ভারে কানেক্ট করা

BLE ডিভাইসের সাথে ইন্টার‍্যাক্ট করার প্রথম ধাপ হল সেটির সাথে কানেক্ট করা। আরও নির্দিষ্টভাবে, ডিভাইসে GATT সার্ভারের সাথে কানেক্ট করা। BLE ডিভাইসে GATT সার্ভারের সাথে কানেক্ট করতে, এই connectGatt() মেথড ব্যবহার করুন। এই পদ্ধতিতে তিনটি প্যারামিটার ব্যবহার করা হয়: a Context অবজেক্ট, autoConnect (BLE ডিভাইস উপলভ্য হওয়ার সাথে সাথেই সেটির সাথে অটোমেটিক কানেক্ট করা হবে কিনা তা নির্দেশ করে এমন একটি বুলিয়ান মান) এবং a BluetoothGattCallback-এর রেফারেন্স:

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

এটি BLE ডিভাইস দ্বারা হোস্ট করা GATT সার্ভারের সাথে কানেক্ট করে এবং একটি BluetoothGatt ইনস্ট্যান্স রিটার্ন করে, যা আপনি তারপর GATT ক্লায়েন্ট অপারেশন পরিচালনা করতে ব্যবহার করতে পারেন। কলার (Android অ্যাপ) হল GATT ক্লায়েন্ট। ক্লায়েন্টকে ফলাফল ডেলিভার করার জন্য BluetoothGattCallback ব্যবহার করা হয়, যেমন কানেকশন স্ট্যাটাস, সেইসাথে আরও GATT ক্লায়েন্ট অপারেশন।

বাউন্ড পরিষেবা সেট-আপ করা

নিচের উদাহরণে, BLE অ্যাপটি ব্লুটুথ ডিভাইসের সাথে কানেক্ট করতে, ডিভাইসের ডেটা দেখাতে এবং ডিভাইসের সাথে কাজ করে এমন GATT পরিষেবা ও বৈশিষ্ট্য দেখাতে একটি অ্যাক্টিভিটি (DeviceControlActivity) প্রদান করে। ব্যবহারকারীর ইনপুটের উপর ভিত্তি করে, এই অ্যাক্টিভিটি BluetoothLeService নামের Service-এর সাথে যোগাযোগ করে, যা BLE API-এর মাধ্যমে BLE ডিভাইসের সাথে ইন্টার‍্যাক্ট করে। বাউন্ড সার্ভিস ব্যবহার করে যোগাযোগ করা হয়, যা অ্যাক্টিভিটিকে BluetoothLeService-এর সাথে কানেক্ট করতে এবং ডিভাইসের সাথে কানেক্ট করার জন্য ফাংশন কল করতে দেয়। 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 সম্পর্কে আরও জানতে Bluetooth সেট-আপ করুন লিঙ্কটি দেখুন। নিচে দেওয়া উদাহরণে এই সেট-আপ কোডকে একটি 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 ডিভাইসের সাথে কানেক্ট করতে পারবে। পরিষেবাটি যাতে কানেকশন শুরু করতে পারে, তার জন্য অ্যাক্টিভিটিকে ডিভাইসের অ্যাড্রেস পরিষেবায় পাঠাতে হবে। ডিভাইস অ্যাক্সেস করার জন্য পরিষেবা প্রথমে BluetoothAdapter-এ getRemoteDevice() কল করবে। অ্যাডাপ্টর যদি সেই অ্যাড্রেস সহ কোনও ডিভাইস খুঁজে না পায়, তাহলে 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 অবজেক্ট ব্যবহার করতে পারে।

এই connectGatt() ফাংশন ব্যবহার করা হয়। এর জন্য একটি Context অবজেক্ট, একটি autoConnect boolean ফ্ল্যাগ এবং 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
        }
    }
}