الخطوة الأولى في التفاعل مع جهاز 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 من خلال واجهة برمجة تطبيقات 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. يجب أن يتحقّق من توفّر المحوّل على الجهاز. اطّلِع على مقالة إعداد
Bluetooth لمزيد من المعلومات حول
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 للوصول إلى الجهاز. إذا تعذّر على المحوّل العثور على جهاز بهذا العنوان، سيظهر الخطأ IllegalArgumentException في getRemoteDevice().
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.
// 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 لتلقّي
إشعارات بشأن حالة الاتصال واكتشاف الخدمات وقراءة السمات
والإشعارات المتعلقة بالسمات.
يركّز هذا الموضوع على إشعارات حالة الاتصال. راجِع نقل بيانات Bluetooth منخفض الطاقة لمعرفة كيفية التعرّف على الخدمات وقراءة السمات وطلب تلقّي إشعارات بشأن السمات.
يتم تفعيل الدالة
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، لذا يتم تمرير 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) } } }
في نقل بيانات Bluetooth Low Energy، يتم استخدام 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 } } }