الاتصال بخادم GATT

تتمثل الخطوة الأولى للتفاعل مع جهاز 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 API من خلال واجهة برمجة تطبيقات 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);
    }
}

إعداد محوِّل البلوتوث

بعد ربط الخدمة، يجب الوصول إلى 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;
        }
    };

    ...
}

الاتصال بجهاز

بعد إعداد BluetoothService، يمكن توصيله بجهاز 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 BluetoothService 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 BluetoothService : 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 BluetoothService 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 BluetoothService extends Service {

...

      @Override
      public boolean onUnbind(Intent intent) {
          close();
          return super.onUnbind(intent);
      }

      private void close() {
          if (bluetoothGatt == null) {
              Return;
          }
          bluetoothGatt.close();
          bluetoothGatt = null;
      }
}