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 অ্যাপ ব্লুটুথ ডিভাইসের সাথে কানেক্ট করতে, ডিভাইসের ডেটা দেখাতে এবং ডিভাইসের মাধ্যমে কাজ করে এমন 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 সম্পর্কে আরও তথ্যের জন্য, ব্লুটুথ সেট-আপ করুন দেখুন। নিম্নলিখিত উদাহরণে এই সেট-আপ কোডকে 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 বুলিয়ান ফ্ল্যাগ এবং 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
        }
    }
}