התחברות לשרת 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) כדי להתחבר למכשירי Bluetooth, להציג את נתוני המכשיר, ולהציג את השירותים והמאפיינים של 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);
    }
}

הגדרת BluetoothAdapter

אחרי שהשירות מקושר, הוא צריך לגשת BluetoothAdapter היא צריכה ולבדוק שהמתאם זמין במכשיר. מומלץ לקרוא את המאמר הגדרה Bluetooth לקבלת מידע נוסף על 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. הטיפול בערך המוחזר 'FALSE' מהפונקציה initialize() תלוי תרגום מכונה. אפשר להציג הודעת שגיאה למשתמש שמציינת המכשיר הנוכחי לא תומך בפעולת Bluetooth או משבית תכונות כלשהן שדורשים Bluetooth כדי לפעול. בדוגמה הבאה, בוצעה קריאה ל-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. לחשבון בדוגמה הבאה, כתובת המכשיר מועברת לפעילות כ-Intent נוסף.

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;
    }
};

הצהרה על קריאה חוזרת (callback) של 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

אחד השלבים החשובים כשמדובר בחיבורי Bluetooth הוא לסגור את אחרי שמסיימים איתו. כדי לעשות זאת, צריך להתקשר אל 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;
      }
}