当前位置:   article > 正文

Android官方文档学习之Bluetooth Low Energy(一)

Android官方文档学习之Bluetooth Low Energy(一)

BLE官方文档

一:基本介绍

Android 在API18/Android4.3版本开始为低功耗蓝牙 (BLE) 提供内置平台支持,并提供应用程序可用于发现设备、查询服务和传输信息的 API。

利用低功耗蓝牙可以实现:1.在附近的硬件设备之间传输少量数据。2.与接近传感器交互,为用户提供基于其当前位置的定制服务。

与经典蓝牙相比,BLE 旨在显着降低功耗。这允许应用程序与具有更严格电源要求的 BLE 设备进行通信,例如接近传感器、心率监测器和健身设备。

另外需要注意的是,当用户使用 BLE 将他们的设备与另一台设备配对时,用户设备上的所有应用程序都可以访问这两个设备之间通信的数据。 出于这个原因,如果应用程序捕获了敏感数据,应该实施应用程序层安全性以保护该数据的隐私。

二.关键术语和概念

支持BLE的设备要在彼此之间传输数据,它们必须首先形成一个通信通道。使用蓝牙 LE API 需要在清单文件中声明多个权限。一旦你的应用程序获得使用蓝牙的权限,您的**应用程序需要访问蓝牙适配器并确定设备上的蓝牙是否可用。**如果蓝牙可用,设备将扫描附近的 BLE 设备。找到设备后,通过连接到 BLE 设备上的 GATT 服务器来发现 BLE 设备的功能建立连接后,可以根据可用服务和特征与连接的设备传输数据。 以下是需要添加的权限:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!--  android M 以上版本获取周边蓝牙设备必须定位权限  -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature
    android:name="android.hardware.bluetooth_le"
    android:required="true" />

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
1.通用属性配置文件 (GATT)

GATT 配置文件是用于通过 BLE 链路发送和接收称为“属性”的短数据片段的通用规范。当前所有的 BLE 应用配置文件都基于 GATT。下面的网址是GitHub 上的 Android BluetoothLeGatt 示例。 https://github.com/android/connectivity-samples/tree/master/BluetoothLeGatt

2.Profiles

蓝牙 SIG 为 BLE 设备定义了许多配置文件(Profiles)。配置文件是关于设备如何在特定应用程序中工作的规范。一台设备可以实现多个配置文件。例如,设备可能包含心率监测器和电池电量检测器。

3.Attribute Protocol (ATT)

GATT 建立在属性协议 (ATT) 之上。这也称为 GATT/ATT。 ATT 针对在 BLE 设备上运行进行了优化。为此,它使用尽可能少的字节。每个属性都由通用唯一标识符 (UUID) 唯一标识,它是用于唯一标识信息的字符串 ID 的标准化 128 位格式。 ATT 传输的属性被格式化为特征和服务。

4.特征(Descriptor

特征包含单个值和描述特征值的 0-n 个描述符。一个特性可以被认为是一种类型,类似于一个类。描述符是描述特征值的已定义属性。例如,描述符可能指定人类可读的描述、特征值的可接受范围或特定于特征值的度量单位。

5.服务(Service

服务是特征的集合。例如,你可以拥有一项名为“心率监测器”的服务,其中包括“心率测量”等特征。你可以在 bluetooth.org 上找到现有的基于 GATT 的配置文件和服务的列表。

三.角色和职责 (Roles and responsibilities)

当设备与 BLE 设备交互时,适用以下角色和职责:

中央与外设。这适用于 BLE 连接本身。**中心角色的设备扫描,寻找广告,外围角色的设备制作广告。**在之后的示例代码中也有设计到广告的定义。

GATT 服务器与 GATT 客户端。这决定了两个设备在建立连接后如何相互通信。要区别它们的不同,假设有一部 Android 手机和一个 BLE 设备的活动跟踪器。电话支持中心角色;活动跟踪器支持外围角色。如果要建立 BLE 连接,您需要包含这两者,仅支持外围设备的两个设备无法相互通信,两个仅支持中央设备的设备也不能相互通信。

一旦手机和活动跟踪器建立连接,它们就会开始相互传输 GATT 元数据。根据他们传输的数据类型,其中一个可能充当服务器。例如,如果活动跟踪器想要向手机报告传感器数据,则活动跟踪器充当服务器可能是有意义的。如果活动跟踪器想要从手机接收更新,那么手机充当服务器可能是有意义的。

在本主题中使用的示例中,应用程序(在 Android 设备上运行)是 GATT 客户端。该应用程序从 GATT 服务器获取数据,该服务器是支持心率配置文件的 BLE 心率监视器。您也可以设计您的应用程序来扮演 GATT 服务器角色。

四.查找设备

要查找 BLE 设备,请使用 startScan() 方法。此方法将 ScanCallback 作为参数。您必须实现此回调,因为这是返回扫描结果的方式。由于扫描会消耗大量电池电量,因此您应遵守以下准则:

找到所需设备后,立即停止扫描。

切勿循环扫描,并始终设置扫描时间限制。以前可用的设备可能已超出范围,继续扫描会耗尽电池电量。 在以下示例中,BLE 应用程序提供了一个活动 (DeviceScanActivity) 来扫描可用的蓝牙 LE 设备并将它们显示在一个列表中给用户。以下代码段显示了如何开始和停止扫描:

private BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
private boolean scanning;
private Handler handler = new Handler();

// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;

private void scanLeDevice() {
    if (!scanning) {
        // Stops scanning after a predefined scan period.
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                scanning = false;
                bluetoothLeScanner.stopScan(leScanCallback);
            }
        }, SCAN_PERIOD);

        scanning = true;
        bluetoothLeScanner.startScan(leScanCallback);
    } else {
        scanning = false;
        bluetoothLeScanner.stopScan(leScanCallback);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

注意:BluetoothLeScanner 仅在设备当前启用蓝牙的情况下可从 BluetoothAdapter 获得。如果未启用蓝牙,则 getBluetoothLeScanner() 返回 null。

要仅扫描特定类型的外围设备,您可以改为调用 startScan(List, ScanSettings, ScanCallback),提供限制扫描查找的设备的 ScanFilter 对象列表和指定有关扫描的参数的 ScanSettings 对象. 以下代码示例是 ScanCallback 的实现,它是用于传递 BLE 扫描结果的接口。找到结果后,会将它们添加到 DeviceScanActivity 中的列表适配器以显示给用户。

private LeDeviceListAdapter leDeviceListAdapter = new LeDeviceListAdapter();

// Device scan callback.
private ScanCallback leScanCallback =
        new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                super.onScanResult(callbackType, result);
                leDeviceListAdapter.addDevice(result.getDevice());
                leDeviceListAdapter.notifyDataSetChanged();
            }
        };

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

注意:您只能扫描蓝牙 LE 设备或扫描经典蓝牙设备,如蓝牙概述中所述。您不能同时扫描蓝牙 LE 和经典设备。

五.连接到GATT服务

与 BLE 设备交互的第一步是连接到它。更具体地说,**连接到设备上的 GATT 服务器。**要连接到 BLE 设备上的 GATT 服务器,请使用 connectGatt() 方法。此方法采用三个参数:一个 Context 对象、autoConnect(一个布尔值,指示是否在 BLE 设备可用时立即自动连接),以及对 BluetoothGattCallback 的引用:

bluetoothGatt = device.connectGatt(this, false, gattCallback);

  • 1
  • 2

这将连接到由 BLE 设备托管的 GATT 服务器,并返回一个 BluetoothGatt 实例,然后您可以使用该实例来执行 GATT 客户端操作。调用者(Android 应用程序)是 GATT 客户端。 BluetoothGattCallback 用于将结果传递给客户端,例如连接状态,以及任何进一步的 GATT 客户端操作。

1.设置绑定服务

在以下示例中,BLE 应用程序提供了一个 Activity(DeviceControlActivity)来连接蓝牙设备,显示设备数据,并显示设备支持的 GATT 服务和特性。根据用户输入,此活动与名为 BluetoothLeService 的服务通信,该服务通过 BLE API 与 BLE 设备交互。使用绑定服务执行通信,该服务允许 Activity 连接到 BluetoothLeService 并调用函数以连接到设备。 BluetoothLeService 需要一个 Binder 实现来提供对活动服务的访问。

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Activity 可以使用 bindService() 启动服务,传入一个 Intent 来启动服务,一个 ServiceConnection 实现来监听连接和断开事件,以及一个标志来指定额外的连接选项。

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) {
                // 在服务上调用函数以检查连接并连接到设备
               
            }
        }

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
2.设置蓝牙适配器

服务绑定后,需要访问蓝牙适配器。它应该检查设备上的适配器是否可用。阅读https://developer.android.google.cn/guide/topics/connectivity/bluetooth/setup?hl=en以获取有关蓝牙适配器的更多信息。

下面的示例将此设置代码包装在一个 initialize() 函数中,该函数返回一个指示成功的布尔值。

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

    ...
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

活动在其 ServiceConnection 实现中调用此函数。处理来自 initialize() 函数的错误返回值取决于您的应用程序。您可以向用户显示一条错误消息,指示当前设备不支持蓝牙操作或禁用任何需要蓝牙才能工作的功能。在以下示例中,在 Activity 上调用 finish() 以将用户返回到上一个屏幕。

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();
                }
                // 执行设备连接
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bluetoothService = null;
        }
    };

    ...
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
3.连接到设备

BluetoothService 初始化后,即可连接到 BLE 设备。活动需要将设备地址发送到服务,以便它可以启动连接。该服务将首先调用 BluetoothAdapter 上的 getRemoteDevice() 来访问设备。如果适配器无法找到具有该地址的设备,getRemoteDevice() 将引发 IllegalArgumentException。

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
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

一旦服务被初始化,DeviceControlActivity 就会调用这个 connect() 函数。 Activity 需要传入 BLE 设备的地址。在以下示例中,设备地址作为额外的意图传递给活动。

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();
            }
            // 执行设备连接
            bluetoothService.connect(deviceAddress);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        bluetoothService = null;
    }
};

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
4.声明 GATT 回调

一旦活动告诉服务连接到哪个设备并且服务连接到设备,服务需要连接到 BLE 设备上的 GATT 服务器。此连接需要 BluetoothGattCallback 来接收有关连接状态、服务发现、特征读取和特征通知的通知。 本主题重点介绍连接状态通知。

当与设备的 GATT 服务器的连接发生变化时,会触发 onConnectionStateChanged() 函数。在以下示例中,回调在 Service 类中定义,因此**一旦服务连接到 BluetoothDevice,它就可以与 BluetoothDevice 一起使用。**也便于服务使用BluetoothDevice来进行下一步的连接和使用。

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
5.连接到 GATT 服务

一旦声明了 BluetoothGattCallback,服务就可以使用 connect() 函数中的 BluetoothDevice 对象连接到设备上的 GATT 服务。

使用了 connectGatt() 函数。这需要一个 Context 对象、一个 autoConnect 布尔标志和 BluetoothGattCallback。在此示例中,应用程序直接连接到 BLE 设备,因此为 autoConnect 传递 false。 还添加了 BluetoothGatt 属性。这允许服务在不再需要时关闭连接。

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
6.广播更新

当服务器与 GATT 服务器连接或断开连接时,它需要通知活动新状态。有几种方法可以做到这一点。以下示例使用广播将信息从服务发送到活动。

该服务声明了一个广播新状态的函数。这个函数接受一个动作字符串,该字符串在被广播到系统之前被传递给一个 Intent 对象。

private void broadcastUpdate(final String action) {
    final Intent intent = new Intent(action);
    sendBroadcast(intent);
}
  • 1
  • 2
  • 3
  • 4

一旦广播功能到位,它就会在 BluetoothGattCallback 中用于发送有关与 GATT 服务器的连接状态的信息。常量和服务的当前连接状态在表示 Intent 操作的服务中声明。

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
7.监听活动更新

一旦服务广播连接更新,活动需要实现一个广播接收器。在设置活动时注册此接收器,并在活动离开屏幕时取消注册。通过监听来自服务的事件,Activity 能够根据与 BLE 设备的当前连接状态更新用户界面。

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

BroadcastReceiver 还用于传达服务发现以及来自设备的特征数据。

8.关闭 GATT 连接

处理蓝牙连接的一个重要步骤是在完成连接后关闭连接。为此,请在 BluetoothGatt 对象上调用 close() 函数。在以下示例中,服务持有对 BluetoothGatt 的引用。当活动与服务解除绑定时,连接将关闭以避免耗尽设备电池。

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

六.传输 BLE 数据

连接到 BLE GATT 服务器后,您可以使用该连接来了解设备上可用的服务、从设备查询数据以及在某个 GATT 特性发生变化时请求通知

1.发现服务

连接到 BLE 设备上的 GATT 服务器后,要做的第一件事就是执行服务发现。这提供了有关远程设备上可用服务以及服务特征及其描述符的信息。在以下示例中,一旦服务成功连接到设备(通过适当调用 BluetoothGattCallback 的 onConnectionStateChange() 函数指示),discoverServices() 函数会从 BLE 设备查询信息。 这个服务需要重写了 BluetoothGattCallback 中的 onServicesDiscovered() 函数。当设备报告其可用服务时会调用此函数。

class BluetoothService extends Service {

    public final static String ACTION_GATT_SERVICES_DISCOVERED =
            "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";

    ...

    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);
                // Attempts to discover services after successful connection.
                bluetoothGatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                // disconnected from the GATT Server
                connectionState = STATE_DISCONNECTED;
                broadcastUpdate(ACTION_GATT_DISCONNECTED);
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }
    };
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

该服务使用广播来通知活动。发现服务后,服务可以调用 getServices() 来获取报告的数据。就是将这些服务的数据放置到一个集合中。

class BluetoothService extends Service {

...

    public List<BluetoothGattService> getSupportedGattServices() {
        if (bluetoothGatt == null) return null;
        return bluetoothGatt.getServices();
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Activity 可以在收到广播意图时调用此函数,表明服务发现已完成。

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);
            } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
                // Show all the supported services and characteristics on the user interface.
                displayGattServices(bluetoothService.getSupportedGattServices());
            }
        }
    };
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
2.读取 BLE 特性

一旦您的应用程序连接到 GATT 服务器并发现服务,它就可以在支持的情况下读取和写入属性。例如,以下代码片段遍历服务器的服务和特征并将它们显示在 UI 中:

public class DeviceControlActivity extends Activity {
    ...
    // Demonstrates how to iterate through the supported GATT
    // Services/Characteristics.
    // In this sample, we populate the data structure that is bound to the
    // ExpandableListView on the UI.
    private void displayGattServices(List<BluetoothGattService> gattServices) {
        if (gattServices == null) return;
        String uuid = null;
        String unknownServiceString = getResources().
                getString(R.string.unknown_service);
        String unknownCharaString = getResources().
                getString(R.string.unknown_characteristic);
        ArrayList<HashMap<String, String>> gattServiceData =
                new ArrayList<HashMap<String, String>>();
        ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
                = new ArrayList<ArrayList<HashMap<String, String>>>();
        mGattCharacteristics =
                new ArrayList<ArrayList<BluetoothGattCharacteristic>>();

        // Loops through available GATT Services.
        for (BluetoothGattService gattService : gattServices) {
            HashMap<String, String> currentServiceData =
                    new HashMap<String, String>();
            uuid = gattService.getUuid().toString();
            currentServiceData.put(
                    LIST_NAME, SampleGattAttributes.
                            lookup(uuid, unknownServiceString));
            currentServiceData.put(LIST_UUID, uuid);
            gattServiceData.add(currentServiceData);

            ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
                    new ArrayList<HashMap<String, String>>();
            List<BluetoothGattCharacteristic> gattCharacteristics =
                    gattService.getCharacteristics();
            ArrayList<BluetoothGattCharacteristic> charas =
                    new ArrayList<BluetoothGattCharacteristic>();
           // Loops through available Characteristics.
            for (BluetoothGattCharacteristic gattCharacteristic :
                    gattCharacteristics) {
                charas.add(gattCharacteristic);
                HashMap<String, String> currentCharaData =
                        new HashMap<String, String>();
                uuid = gattCharacteristic.getUuid().toString();
                currentCharaData.put(
                        LIST_NAME, SampleGattAttributes.lookup(uuid,
                                unknownCharaString));
                currentCharaData.put(LIST_UUID, uuid);
                gattCharacteristicGroupData.add(currentCharaData);
            }
            mGattCharacteristics.add(charas);
            gattCharacteristicData.add(gattCharacteristicGroupData);
         }
    ...
    }
...
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

GATT 服务提供了可以从设备读取的特征列表。为了查询数据,调用 BluetoothGatt 上的 readCharacteristic() 函数,传入您要读取的 BluetoothGattCharacteristic。

class BluetoothService extends Service {
    public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
        if (bluetoothGatt == null) {
            Log.w(TAG, "BluetoothGatt not initialized");
            return;
        }
        bluetoothGatt.readCharacteristic(characteristic);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在此示例中,服务实现了一个函数来调用 readCharacteristic()。这是一个异步调用。结果被发送到 BluetoothGattCallback 函数 onCharacteristicRead()。

class BluetoothService extends Service {
    private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
        @Override
        public void onCharacteristicRead(
        BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic,
        int status
        ) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }
    };
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

当触发特定回调时,它会调用适当的 broadcastUpdate() 辅助方法并向其传递一个操作。请注意,本节中的数据解析是根据蓝牙心率测量配置文件规范执行的。

private void broadcastUpdate(final String action,
                             final BluetoothGattCharacteristic characteristic) {
    final Intent intent = new Intent(action);

    // This is special handling for the Heart Rate Measurement profile. Data
    // parsing is carried out as per profile specifications.
    if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
        int flag = characteristic.getProperties();
        int format = -1;
        if ((flag & 0x01) != 0) {
            format = BluetoothGattCharacteristic.FORMAT_UINT16;
            Log.d(TAG, "Heart rate format UINT16.");
        } else {
            format = BluetoothGattCharacteristic.FORMAT_UINT8;
            Log.d(TAG, "Heart rate format UINT8.");
        }
        final int heartRate = characteristic.getIntValue(format, 1);
        Log.d(TAG, String.format("Received heart rate: %d", heartRate));
        intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
    } else {
        // For all other profiles, writes the data formatted in HEX.
        final byte[] data = characteristic.getValue();
        if (data != null && data.length > 0) {
            final StringBuilder stringBuilder = new StringBuilder(data.length);
            for(byte byteChar : data)
                stringBuilder.append(String.format("%02X ", byteChar));
            intent.putExtra(EXTRA_DATA, new String(data) + "\n" +
                    stringBuilder.toString());
        }
    }
    sendBroadcast(intent);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
3.接收 GATT 通知

当设备上的特定特征发生变化时,BLE 应用程序通常会要求得到通知。在以下示例中,服务实现了一个函数来调用 setCharacteristicNotification() 方法:

class BluetoothService extends Service {
    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enabled) {
        if (bluetoothGatt == null) {
            Log.w(TAG, "BluetoothGatt not initialized");
            Return;
        }
        bluetoothGatt.setCharacteristicNotification(characteristic, enabled);

        // This is specific to Heart Rate Measurement.
        if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            bluetoothGatt.writeDescriptor(descriptor);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

一旦为特征启用通知,如果远程设备上的特征发生变化,就会触发 onCharacteristicChanged() 回调:

class BluetoothService extends Service {
    private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
        @Override
        public void onCharacteristicChanged(
        BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic
        ) {
            broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
        }
    };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/573711
推荐阅读
相关标签
  

闽ICP备14008679号