تتمثل الخطوة الأولى للتفاعل مع جهاز BLE في الاتصال به. وبشكل أكثر تحديدًا، الاتصال بخادم GATT على الجهاز. للاتصال بخادم GATT على جهاز BLE، استخدِم الطريقة
connectGatt()
. تستخدم هذه الطريقة ثلاث معلَمات: كائن Context
، وautoConnect
(قيمة منطقية تشير إلى ما إذا كان سيتم الاتصال تلقائيًا بجهاز BLE فور توفّره)، ومرجعًا لـ BluetoothGattCallback
:
Kotlin
var bluetoothGatt: BluetoothGatt? = null ... bluetoothGatt = device.connectGatt(this, false, gattCallback)
Java
bluetoothGatt = device.connectGatt(this, false, gattCallback);
يتصل هذا بخادم GATT الذي يستضيفه جهاز BLE، ويعرض مثيل BluetoothGatt
الذي يمكنك استخدامه بعد ذلك لإجراء عمليات عميل GATT. المتصل (تطبيق Android)
هو عميل GATT. وتُستخدم BluetoothGattCallback
لتقديم النتائج للعميل، مثل حالة الاتصال، وكذلك أي عمليات أخرى لعميل GATT.
إعداد خدمة مرتبطة
في المثال التالي، يوفّر تطبيق BLE نشاطًا
(DeviceControlActivity
) للاتصال بالأجهزة التي تتضمّن بلوتوث وعرض بيانات الجهاز
وعرض خدمات GATT وخصائصها المتوافقة مع الجهاز. استنادًا إلى البيانات التي أدخلها المستخدم، يتواصل هذا النشاط مع Service
يُسمى BluetoothLeService
، ويتفاعل مع جهاز BLE عبر واجهة BLE API. ويتم إجراء الاتصال باستخدام خدمة مرتبطة تسمح للنشاط بالاتصال بـ BluetoothLeService
ووظائف الاتصال للاتصال بالأجهزة. يحتاج BluetoothLeService
إلى تنفيذ
Binder
يتيح الوصول إلى
الخدمة للنشاط.
Kotlin
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 } } }
Java
class BluetoothLeService extends Service { private Binder binder = new LocalBinder(); @Nullable @Override public IBinder onBind(Intent intent) { return binder; } class LocalBinder extends Binder { public BluetoothLeService getService() { return BluetoothLeService.this; } } }
يمكن للنشاط بدء الخدمة باستخدام
bindService()
،
وتمرير Intent
لبدء
الخدمة، وServiceConnection
تنفيذ للاستماع إلى أحداث الاتصال والانقطاع، وعلامة
لتحديد خيارات الاتصال الإضافية.
Kotlin
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) } }
Java
class DeviceControlActivity extends AppCompatActivity { private BluetoothLeService bluetoothService; private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { // call functions on service to check connection and connect to devices } } @Override public void onServiceDisconnected(ComponentName name) { bluetoothService = null; } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.gatt_services_characteristics); Intent gattServiceIntent = new Intent(this, BluetoothLeService.class); bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE); } }
إعداد Bluetooth Adapter
بعد ربط الخدمة، يجب الوصول إلى
BluetoothAdapter
. يجب أن يتحقق من توفُّر المحوّل على الجهاز. لمزيد من المعلومات حول BluetoothAdapter
، يمكنك الاطّلاع على إعداد
البلوتوث. يلتف المثال التالي رمز الإعداد هذا في دالة initialize()
تعرض قيمة Boolean
تشير إلى النجاح.
Kotlin
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 } ... }
Java
class BluetoothLeService extends Service { public static final String TAG = "BluetoothLeService"; private BluetoothAdapter bluetoothAdapter; public boolean initialize() { bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter."); return false; } return true; } ... }
يستدعي النشاط هذه الدالة ضمن تنفيذ ServiceConnection
.
وتعتمد التعامل مع القيمة المعروضة الخاطئة من الدالة initialize()
على تطبيقك. قد تعرض رسالة خطأ للمستخدم تشير إلى أنّ الجهاز الحالي لا يتيح تشغيل البلوتوث أو إيقاف أي ميزات تتطلّب تفعيل البلوتوث. في المثال التالي، يتم استدعاء finish()
في النشاط لإعادة المستخدم إلى الشاشة السابقة.
Kotlin
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 } } ... }
Java
class DeviceControlsActivity extends AppCompatActivity { private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { if (!bluetoothService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); finish(); } // perform device connection } } @Override public void onServiceDisconnected(ComponentName name) { bluetoothService = null; } }; ... }
الاتصال بجهاز
بعد إعداد مثيل BluetoothLeService
، يمكنه الاتصال بجهاز BLE. يحتاج النشاط إلى إرسال عنوان الجهاز إلى الخدمة حتى يتمكّن من بدء الاتصال. ستتصل الخدمة أولاً بـ
getRemoteDevice()
على BluetoothAdapter
للوصول إلى الجهاز. إذا لم يتمكن المحوّل من العثور على
جهاز يحمل هذا العنوان، يعرض getRemoteDevice()
الرمز
IllegalArgumentException
.
Kotlin
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 } ?: run { Log.w(TAG, "BluetoothAdapter not initialized") return false } }
Java
public boolean connect(final String address) { if (bluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } try { final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); } catch (IllegalArgumentException exception) { Log.w(TAG, "Device not found with provided address."); return false; } // connect to the GATT server on the device }
يستدعي DeviceControlActivity
الدالة connect()
هذه بعد إعداد الخدمة. ويجب تمرير النشاط في عنوان جهاز BLE. في المثال التالي، يتم تمرير عنوان الجهاز إلى النشاط كهدف إضافي.
Kotlin
// 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 bluetooth.connect(deviceAddress) } } override fun onServiceDisconnected(componentName: ComponentName) { bluetoothService = null } }
Java
private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { bluetoothService = ((LocalBinder) service).getService(); if (bluetoothService != null) { if (!bluetoothService.initialize()) { Log.e(TAG, "Unable to initialize Bluetooth"); finish(); } // perform device connection bluetoothService.connect(deviceAddress); } } @Override public void onServiceDisconnected(ComponentName name) { bluetoothService = null; } };
تعريف معاودة الاتصال بـ GATT
وبعد أن يخبر النشاط الخدمة عن الجهاز الذي يجب الاتصال به وتتصل الخدمة بالجهاز، يجب أن تتصل الخدمة بخادم GATT على جهاز BLE. يتطلّب هذا الربط BluetoothGattCallback
لتلقّي إشعارات حول حالة الاتصال واكتشاف الخدمة وقراءات الخصائص والإشعارات الخاصة بالسمات.
يركز هذا الموضوع على إشعارات حالة الاتصال. يمكنك الاطّلاع على نقل بيانات BLE لمعرفة كيفية اكتشاف الخدمة وقراءات السمات وطلب الإشعارات الخاصة بالميزات.
يتم تفعيل
الدالة onConnectionStateChange()
عند تغيُّر الاتصال بخادم GATT الخاص بالجهاز.
في المثال التالي، يتم تحديد عملية الاستدعاء في الفئة Service
بحيث يمكن
استخدامها مع
BluetoothDevice
بعد اتصال الخدمة بها.
Kotlin
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 } } }
Java
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 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
. يتيح ذلك للخدمة إغلاق
الاتصال عندما لا تعود هناك
ضرورة.
Kotlin
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 } } }
Java
class BluetoothLeService extends Service { ... private BluetoothGatt bluetoothGatt; ... public boolean connect(final String address) { if (bluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); return false; } try { final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); // connect to the GATT server on the device bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback); return true; } catch (IllegalArgumentException exception) { Log.w(TAG, "Device not found with provided address. Unable to connect."); return false; } } }
بث آخر الأخبار
عندما يتصل الخادم بخادم GATT أو ينقطع الاتصال به، يجب عليه إشعار نشاط الحالة الجديدة. وهناك عدة طرق لتحقيق ذلك. يستخدم المثال التالي عمليات البث لإرسال المعلومات من الخدمة إلى النشاط.
تعلن الخدمة عن دالة لبث الحالة الجديدة. تتخذ هذه الدالة
سلسلة إجراء يتم تمريرها إلى كائن Intent
قبل بثها
إلى النظام.
Kotlin
private fun broadcastUpdate(action: String) { val intent = Intent(action) sendBroadcast(intent) }
Java
private void broadcastUpdate(final String action) { final Intent intent = new Intent(action); sendBroadcast(intent); }
بعد تفعيل وظيفة البث، يتم استخدامها ضمن BluetoothGattCallback
لإرسال معلومات حول حالة الاتصال إلى خادم GATT. يتم تعريف الثوابت وحالة الاتصال الحالية للخدمة
في الخدمة التي تمثّل إجراءات Intent
.
Kotlin
class BluetoothLeService : Service() { private var connectionState = STATE_DISCONNECTED private val bluetoothGattCallback: 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 } }
Java
class BluetoothLeService extends Service { public final static String ACTION_GATT_CONNECTED = "com.example.bluetooth.le.ACTION_GATT_CONNECTED"; public final static String ACTION_GATT_DISCONNECTED = "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED"; private static final int STATE_DISCONNECTED = 0; private static final int STATE_CONNECTED = 2; private int connectionState; ... private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 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); } } }; … }
الاستماع إلى آخر المعلومات في النشاط
بعد أن تنشر الخدمة تعديلات الاتصال، يجب أن ينفّذ النشاط
BroadcastReceiver
.
يمكنك تسجيل هذا الجهاز المُستلِم عند إعداد النشاط، وإلغاء تسجيله عند
مغادرة النشاط للشاشة. ومن خلال الاستماع إلى الأحداث من الخدمة، يتمكن النشاط من تحديث واجهة المستخدم بناءً على حالة الاتصال الحالية بجهاز BLE.
Kotlin
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()) if (bluetoothService != null) { val result = bluetoothService!!.connect(deviceAddress) Log.d(DeviceControlsActivity.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) } } }
Java
class DeviceControlsActivity extends AppCompatActivity { ... private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) { connected = true; updateConnectionState(R.string.connected); } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) { connected = false; updateConnectionState(R.string.disconnected); } } }; @Override protected void onResume() { super.onResume(); registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter()); if (bluetoothService != null) { final boolean result = bluetoothService.connect(deviceAddress); Log.d(TAG, "Connect request result=" + result); } } @Override protected void onPause() { super.onPause(); unregisterReceiver(gattUpdateReceiver); } private static IntentFilter makeGattUpdateIntentFilter() { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED); intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED); return intentFilter; } }
في نقل بيانات BLE،
تُستخدَم السمة BroadcastReceiver
أيضًا للإشارة إلى اكتشاف الخدمة
وبيانات الخصائص الواردة من الجهاز.
إغلاق اتصال GATT
تتمثل إحدى الخطوات المهمة عند التعامل مع اتصالات البلوتوث في إغلاق الاتصال عند الانتهاء منه. ولإجراء ذلك، عليك استدعاء الدالة close()
في الكائن BluetoothGatt
. في المثال التالي، تحتفظ الخدمة
بالإشارة إلى السمة BluetoothGatt
. وعند إلغاء ربط النشاط بالخدمة، يتم إغلاق الاتصال لتجنُّب استنزاف بطارية الجهاز.
Kotlin
class BluetoothLeService : Service() { ... override fun onUnbind(intent: Intent?): Boolean { close() return super.onUnbind(intent) } private fun close() { bluetoothGatt?.let { gatt -> gatt.close() bluetoothGatt = null } } }
Java
class BluetoothLeService extends Service { ... @Override public boolean onUnbind(Intent intent) { close(); return super.onUnbind(intent); } private void close() { if (bluetoothGatt == null) { Return; } bluetoothGatt.close(); bluetoothGatt = null; } }