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

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

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

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

زمانی که activity به سرویس می‌گوید به کدام دستگاه متصل شود و سرویس به دستگاه متصل شود، سرویس باید به سرور GATT روی دستگاه BLE متصل شود. این اتصال به BluetoothGattCallback نیاز دارد تا اعلان‌هایی درباره وضعیت اتصال، کشف سرویس، خواندن مشخصه‌ها و اعلان‌های مشخصه دریافت کند.

این مبحث بر اعلان‌های وضعیت اتصال تمرکز دارد. برای نحوه انجام کشف سرویس، خواندن مشخصه‌ها و درخواست اعلان‌های مشخصه، به بخش انتقال داده‌های BLE مراجعه کنید.

تابع onConnectionStateChange() زمانی فعال می‌شود که اتصال به سرور GATT دستگاه تغییر کند. در مثال زیر، تابع فراخوانی در کلاس 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 متصل می‌شود، بنابراین برای 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 متصل یا از آن جدا می‌شود، باید وضعیت جدید را به activity اطلاع دهد. روش‌های مختلفی برای انجام این کار وجود دارد. مثال زیر از broadcastها برای ارسال اطلاعات از سرویس به activity استفاده می‌کند.

این سرویس تابعی را برای پخش وضعیت جدید تعریف می‌کند. این تابع یک رشته اکشن دریافت می‌کند که قبل از پخش به سیستم، به یک شیء 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
        }
    }
}