התחברות לשרת GATT

השלב הראשון באינטראקציה עם מכשיר BLE הוא התחברות אליו. סמל האפשרויות הנוספות באופן ספציפי, התחברות לשרת GATT במכשיר. כדי להתחבר ל-GATT במכשיר BLE, צריך להשתמש connectGatt() . השיטה הזו כוללת שלושה פרמטרים: אובייקט Context, autoConnect (ערך בוליאני שמציין אם להתחבר באופן אוטומטי למכשיר BLE ברגע הופך לזמין), והפניה BluetoothGattCallback:

KotlinJava
var bluetoothGatt: BluetoothGatt? = null
...

bluetoothGatt
= device.connectGatt(this, false, gattCallback)
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 שמעניקה גישה אל השירות עבור הפעילות.

KotlinJava
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
       
}
   
}
}
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 ההטמעה כדי להאזין לאירועי החיבור והניתוק, וגם דגל כדי לציין אפשרויות חיבור נוספות.

KotlinJava
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)
   
}
}
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 שמציין שהפעולה הסתיימה בהצלחה.

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

   
...
}
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() בפעילות כדי לשלוח את המשתמש חזרה למסך הקודם.

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

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

KotlinJava
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
   
}
}
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 נוסף.

KotlinJava
// 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
   
}
}
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 אחרי מתחבר אליו.

KotlinJava
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
       
}
   
}
}
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. כך השירות יכול לסגור את חיבור כשאין חיבור לאינטרנט. לזמן ממושך יותר.

KotlinJava
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
       
}
   
}
}
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 לפני השידור למערכת.

KotlinJava
private fun broadcastUpdate(action: String) {
   
val intent = Intent(action)
    sendBroadcast
(intent)
}
private void broadcastUpdate(final String action) {
   
final Intent intent = new Intent(action);
    sendBroadcast
(intent);
}

ברגע שפונקציית השידור נמצאת במקום, משתמשים בה בתוך BluetoothGattCallback כדי לשלוח מידע על מצב החיבור עם שרת GATT. קבועים ומצב החיבור הנוכחי של השירות מוצהרים בשירות שמייצג את הפעולות של Intent.

KotlinJava
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

   
}
}

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.

KotlinJava
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)
       
}
   
}
}
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. לאחר ביטול הקישור של הפעילות אל השירות, החיבור נסגר כדי למנוע את התרוקנות הסוללה של המכשיר.

KotlinJava
class BluetoothLeService : Service() {

...

   
override fun onUnbind(intent: Intent?): Boolean {
        close
()
       
return super.onUnbind(intent)
   
}

   
private fun close() {
        bluetoothGatt
?.let { gatt ->
            gatt
.close()
            bluetoothGatt
= null
       
}
   
}
}
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;
     
}
}