একটি GATT সার্ভারের সাথে সংযোগ করুন৷

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

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

এটি BLE ডিভাইস দ্বারা হোস্ট করা GATT সার্ভারের সাথে সংযোগ স্থাপন করে এবং একটি BluetoothGatt ইনস্ট্যান্স ফেরত দেয়, যা আপনি পরবর্তীতে GATT ক্লায়েন্ট অপারেশন পরিচালনা করতে ব্যবহার করতে পারেন। কলার (অ্যান্ড্রয়েড অ্যাপ) হলো 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 ইমপ্লিমেন্টেশন এবং অতিরিক্ত সংযোগ বিকল্পগুলো নির্দিষ্ট করার জন্য একটি `flag` পাস করতে হয়।

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 সম্পর্কে আরও তথ্যের জন্য ‘Set up 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() ফাংশন থেকে false রিটার্ন ভ্যালু কীভাবে হ্যান্ডেল করা হবে তা আপনার অ্যাপ্লিকেশনের উপর নির্ভর করে। আপনি ব্যবহারকারীকে একটি এরর মেসেজ দেখাতে পারেন, যা নির্দেশ করে যে বর্তমান ডিভাইসটি ব্লুটুথ অপারেশন সাপোর্ট করে না, অথবা ব্লুটুথের উপর নির্ভরশীল যেকোনো ফিচার নিষ্ক্রিয় করে দিতে পারেন। নিচের উদাহরণে, ব্যবহারকারীকে আগের স্ক্রিনে ফেরত পাঠানোর জন্য অ্যাক্টিভিটিতে 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 ডিভাইসের অ্যাড্রেসটি পাস করতে হয়। নিচের উদাহরণে, ডিভাইসের অ্যাড্রেসটি একটি intent extra হিসেবে অ্যাক্টিভিটিতে পাস করা হয়েছে।

// 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 ডিক্লেয়ার করা হলে, সার্ভিসটি connect() ফাংশন থেকে BluetoothDevice অবজেক্টটি ব্যবহার করে ডিভাইসের GATT সার্ভিসের সাথে কানেক্ট করতে পারে।

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)
}

ব্রডকাস্ট ফাংশনটি একবার চালু হয়ে গেলে, GATT সার্ভারের সাথে সংযোগের অবস্থা সম্পর্কে তথ্য পাঠানোর জন্য এটি BluetoothGattCallback এর মধ্যে ব্যবহৃত হয়। 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 ডিভাইস থেকে সার্ভিস ডিসকভারি এবং ক্যারেক্টারিস্টিক ডেটা উভয়ই প্রেরণের জন্য ব্যবহৃত হয়।

গ্যাট সংযোগ বন্ধ করুন

ব্লুটুথ সংযোগ নিয়ে কাজ করার সময় একটি গুরুত্বপূর্ণ পদক্ষেপ হলো, কাজ শেষ হয়ে গেলে সংযোগটি বন্ধ করে দেওয়া। এটি করার জন্য, 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
        }
    }
}