متصل شدن به سرور GATT

اولین گام در تعامل با دستگاه BLE، اتصال به آن است. به‌طور دقیق‌تر، اتصال به سرور GATT در دستگاه. برای اتصال به سرور GATT در دستگاه BLE، از روش connectGatt() استفاده کنید. این روش سه پارامتر می‌گیرد: یک Context شیء، autoConnect (یک مقدار بولی که نشان می‌دهد آیا به‌محض دردسترس قرار گرفتن دستگاه BLE به‌طور خودکار به آن متصل شود یا نه)، و مرجعی به BluetoothGattCallback:

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

این تابع به سرور GATT میزبانی‌شده توسط دستگاه BLE متصل می‌شود و نمونه‌ای از BluetoothGatt را برمی‌گرداند که سپس می‌توانید از آن برای انجام عملیات کلاینت GATT استفاده کنید. تماس‌گیرنده (برنامه Android) کلاینت GATT است. از BluetoothGattCallback برای ارائه نتایج به کارخواه، مانند وضعیت اتصال، و همچنین هرگونه عملیات تکمیلی کارخواه GATT استفاده می‌شود.

راه‌اندازی سرویس محدود

در مثال زیر، برنامه BLE فعالیتی (DeviceControlActivity) برای اتصال به دستگاه‌های بلوتوث، نمایش داده‌های دستگاه، و نمایش خدمات و مشخصه‌های GATT پشتیبانی‌شده توسط دستگاه ارائه می‌دهد. این فعالیت براساس ورودی کاربر با Service به‌نام BluetoothLeService ارتباط برقرار می‌کند که ازطریق 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 متصل شود. فعالیت باید نشانی دستگاه را به سرویس ارسال کند تا سرویس بتواند اتصال را آغاز کند. این سرویس ابتدا با 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

وقتی فعالیت به سرویس می‌گوید به کدام دستگاه متصل شود و سرویس به دستگاه متصل می‌شود، سرویس باید به سرور GATT در دستگاه BLE متصل شود. این اتصال برای دریافت اعلان‌های مربوط به وضعیت اتصال، شناسایی سرویس، خواندن مشخصه‌ها، و اعلان‌های مشخصه به 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، سرویس می‌تواند از شیء BluetoothDevice از تابع connect() برای اتصال به سرویس GATT در دستگاه استفاده کند.

از تابع connectGatt() استفاده می‌شود. این کار به شیء Context، پرچم بولی autoConnect، و BluetoothGattCallback نیاز دارد. در این مثال، برنامه مستقیماً به دستگاه BLE متصل می‌شود، بنابراین false برای autoConnect ارسال می‌شود.

دارایی 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

یکی از مراحل مهم هنگام کار با اتصالات بلوتوث، بستن اتصال پس‌از اتمام کار است. برای انجام این کار، تابع close() را در شیء BluetoothGatt فراخوانی کنید. در مثال زیر، سرویس مرجع 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
        }
    }
}