Android蓝牙低功耗开发

     Bluetooth Low Energy(蓝牙低功耗开发)


关键术语和概念


以下是蓝牙低功耗关键术语与概念的一个总结:

  • Generic Attribute Profile (GATT)— GATT profile 是一个通过 BLE 连接来发送和接收名为 "attributes"的小块数据的通用 profile. 当前所有低功耗应用的 profiles 都是基于GATT.
    •  Bluetooth SIG 为蓝牙低功耗设备定义了许多 profiles . 一个 profile 是用来表示某一设备如何工作于特定程序上的一个规格.需要注意的是一个设备可以实现不止一个 profile.例如, 一个设备可以同时包含心率监测器和电量级别检测器.
  • Attribute Protocol (ATT)— GATT 建立在 Attribute Protocol (ATT)之上. 它也可以指代 GATT/ATT. ATT 是为运行在 BLE 设备上而优化过的. 它使用尽可能少的字节.每一个 attribute 都是通过Universally Unique Identifier (UUID)定义的独一无二的,作为一个标准的 128-bit 格式字符串 ID 来识别不同的信息. ATT 通过将attributes格式化为characteristicsservices 来传输.
  • Characteristic— 一个 characteristic 包含了一个单独的值和 0-n 个descriptors 来描述 characteristic 的值. 一个 characteristic 可以被认为是一种类型, 类似于一个类. 
  • Descriptor—  Descriptors 是被定义的描述 characteristic 值的 attributes  . 例如, 一个 descriptor 可能指定了一个人类可读的 描述, 一个 characteristic 值的可接受范围, 或者一个被指定来作为 characteristic 值的测量单位.
  • Service— 一个 service是一系列characteristics 的集合. 例如, 你可以拥有一个service 叫做"Heart Rate Monitor" 包含了如"heart rate measurement."之类的characteristics. 你可以在bluetooth.org上发现一系列已存的 基于GATT的 profileis 和 services .


角色与职责

以下是当一个 Android 设备和一个 BLE 设备交互时的角色与职责:

  • 中央 vs. 外围. 此条应用于 BLE 连接本身. 中央设备的角色负责扫描, 查找 advertisement, 外围角色的设备作为 advertisement.
  • GATT 服务器 vs. GATT 客户端. 此条决定了一旦两个设备建立了连接,它们如何沟通.

要理解这个区别, 想象你拥有一个Android 手机和一个作为BLE 设备的活动追踪器. 手机作为中央角色; 活动追踪器作为外围角色 (为了建立一个BLE 连接你需要它们—--两样都只支持作为外围角色的设备,或者都只支持作为中央角色的设备是无法彼此通讯的).

一旦这个手机和活动追踪器建立了连接, 他们开始互相传输 GATT 元数据 . 基于他们传输数据的类型,当中的一部将扮演服务器角色. 例如, 如果活动追踪器想给手机提供传感器数据, 某种意义上活动追踪器将扮演服务器角色. 如果活动追踪器想要从手机接收更新, 那么手机将作为某种意义上的服务器.

作为文档中使用的例子, Android 程序 (运行在一部Android设备上) 是一个 GATT 客户端. 程序接收来自 GATT 服务器的数据, 这个服务器是个提供了HeartRate Profile的BLE 心率监听器 . 当然你可以选择性的让你的程序作为 GATT 服务器. 详细参考BluetoothGattServer 以获取更多信息.


BLE 权限


    为了在你的程序里使用蓝牙组件, 你必须声明蓝牙权限 BLUETOOTH.你需要这个权限来执行任何蓝牙通讯,比如请求连接, 接受连接, 以及数据传输.

如果你想要你的app能够发起蓝牙搜索或者操作蓝牙设置,你必须声明这个权限 BLUETOOTH_ADMIN.注意: 如果你使用了BLUETOOTH_ADMIN 这个权限, 那你必须同时声明BLUETOOTH 权限.

    在你程序的 manifest 里面声明权限. 例如:

  <uses-permission android:name="android.permission.BLUETOOTH"/>
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

如果你想要声明你的程序只能在支持BLE特性的设备上运行,请在你程序的 manifest 里面加入以下内容:

  <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

    当然,如果你想要使你的程序在不支持BLE的设备上也能运行,你同样要在 manifest 里加入上述声明, 但是需要修改设置required="false". 然后在程序运行的时候你可以通过使用PackageManager.hasSystemFeature()来判定BLE是否被支持:

// Use this check to determine whether BLE is supported on the device. Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    finish();
}

设置 BLE


在你的程序通过 BLE 运行之前, 你需要验证当前设备是支持 BLE的 , 之后, 再确定它是激活的.注意只有当<uses-feature.../>设置为false的时候这才是必要的.

如果不支持BLE, 那你可以优雅的关掉BLE特性. 如果支持 BLE , 但是被禁用了, 那么你可以在程序内请求用户开启蓝牙. 这个设置需要两步来完成, 使用BluetoothAdapter.

  1. 获取  BluetoothAdapter

    所有的蓝牙活动都需要 BluetoothAdapterBluetoothAdapter 代表着设备本身的蓝牙适配器 (蓝牙通讯). 整个系统拥有一个蓝牙适配器, 而你的程序可以通过它来交互. 下面的片段展示了如何获取这个适配器. 主意这个方法通过getSystemService() 来获取一个BluetoothManager 的实例,  而BluetoothManager又是获取适配器所需要的. Android 4.3 (API Level 18) 引入了 BluetoothManager:

    // Initializes Bluetooth adapter.
    final BluetoothManager bluetoothManager =
            (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    mBluetoothAdapter = bluetoothManager.getAdapter();

  2. 开启 Bluetooth

    接下来, 你需要确保蓝牙是开启的. 使用 isEnabled() 去检查蓝牙当前是否开启. 如果这个方法返回了 false, 那么蓝牙被禁用了.如下片段展示了检查蓝牙是否开启. 如果没开启, 它会弹出一个错误提示来让用户到设置里开启蓝牙:

    private BluetoothAdapter mBluetoothAdapter;
    ...
    // Ensures Bluetooth is available on the device and it is enabled. If not,
    // displays a dialog requesting user permission to enable Bluetooth.
    if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    }


发现 BLE 设备


想要发现BLE 设备, 你可以使用 startLeScan() 方法.这个方法带了一个参数BluetoothAdapter.LeScanCallback. 你必须实现这个回调函数, 因为它定义了扫描结果如何返回. 因为扫描是相当耗电的, 你必须遵循以下规则:

  • 一旦发现期待的设备,停止扫描.
  • 不用循环扫描, 设置一个扫描时间限制. 一个之前可用的设备可能会移出范围, 继续扫描会耗干电量.

    如下片段展示了如何开始和结束搜索:

/**
 * Activity for scanning and displaying available BLE devices.
 */
public class DeviceScanActivity extends ListActivity {

    private BluetoothAdapter mBluetoothAdapter;
    private boolean mScanning;
    private Handler mHandler;

    // Stops scanning after 10 seconds.
    private static final long SCAN_PERIOD = 10000;
    ...
    private void scanLeDevice(final boolean enable) {
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScanning = false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);

            mScanning = true;
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
        ...
    }
...
}

    如果你想扫描指定类型的周边设备, 你可以转而使用startLeScan(UUID[], BluetoothAdapter.LeScanCallback),提供一个指定了你的程序支持的GATTservices的UUID 类型的数组 .

以下是BluetoothAdapter.LeScanCallback的一个实现,它是一个用来传递扫描结果的接口:

private LeDeviceListAdapter mLeDeviceListAdapter;
...
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
        runOnUiThread(new Runnable() {
           @Override
           public void run() {
               mLeDeviceListAdapter.addDevice(device);
               mLeDeviceListAdapter.notifyDataSetChanged();
           }
       });
   }
};

注意: 你只能扫描蓝牙低功耗设备或者传统蓝牙设备, 在Bluetooth里描述的那些. 你不能同时扫描蓝牙低功耗设备和传统蓝牙设备.


连接到一个 GATT 服务器


    和一个 BLE 设备交互的第一步是连接它—更确切的说, 连接到此设备的 GATT 服务器. 要连接到一个BLE的 GATT 服务器, 你可以使用connectGatt() 方法.这个方法带三个参数: 一个Context 对象,autoConnect (boolean 值用来决定是否一旦一个 BLE 设备可用就自动连接到它), 以及一个BluetoothGattCallback 的引用:

    mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

这个与 GATT 服务器的连接由 BLE 设备管理, 并且返回了一个 BluetoothGatt 实例, 而通过这个实例你可以进行 GATT 客户端操作. 调用者 (即此 Android 程序) 即为 GATT 客户端. BluetoothGattCallback 用来为客户端传递结果, 例如连接状态, 以及一些更深入的 GATT 客户端操作.

    在这个例子当中,  BLE 程序提供了一个页面(DeviceControlActivity)去连接,展示数据, 并且展示了该设备所支持的 GATT services 和 characteristics. 基于用户输入,该页面和一个名为BluetoothLeServiceService 通讯,而它通过Android BLE API和 BLE 设备 进行交互:

// A service that interacts with the BLE device via the Android BLE API.
public class BluetoothLeService extends Service {
    private final static String TAG = BluetoothLeService.class.getSimpleName();

    private BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;
    private String mBluetoothDeviceAddress;
    private BluetoothGatt mBluetoothGatt;
    private int mConnectionState = STATE_DISCONNECTED;

    private static final int STATE_DISCONNECTED = 0;
    private static final int STATE_CONNECTING = 1;
    private static final int STATE_CONNECTED = 2;

    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";
    public final static String ACTION_GATT_SERVICES_DISCOVERED =
            "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
    public final static String ACTION_DATA_AVAILABLE =
            "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
    public final static String EXTRA_DATA =
            "com.example.bluetooth.le.EXTRA_DATA";

    public final static UUID UUID_HEART_RATE_MEASUREMENT =
            UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);

    // Various callback methods defined by the BLE API.
    private final BluetoothGattCallback mGattCallback =
            new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status,
                int newState) {
            String intentAction;
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                intentAction = ACTION_GATT_CONNECTED;
                mConnectionState = STATE_CONNECTED;
                broadcastUpdate(intentAction);
                Log.i(TAG, "Connected to GATT server.");
                Log.i(TAG, "Attempting to start service discovery:" +
                        mBluetoothGatt.discoverServices());

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = ACTION_GATT_DISCONNECTED;
                mConnectionState = STATE_DISCONNECTED;
                Log.i(TAG, "Disconnected from GATT server.");
                broadcastUpdate(intentAction);
            }
        }

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

        @Override
        // Result of a characteristic read operation
        public void onCharacteristicRead(BluetoothGatt gatt,
                BluetoothGattCharacteristic characteristic,
                int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }
     ...
    };
...
}

    当一个特定的回调出发的时候, 它会调用合适的 broadcastUpdate() 帮助方法 并且传递给它一个 action. 注意数据解析在这一部分是和蓝牙心率测量profile specifications一致执行的:

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

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

返回 DeviceControlActivity,这些时间会被一个 BroadcastReceiver 处理:

// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a
// result of read or notification operations.
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
            mConnected = true;
            updateConnectionState(R.string.connected);
            invalidateOptionsMenu();
        } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
            mConnected = false;
            updateConnectionState(R.string.disconnected);
            invalidateOptionsMenu();
            clearUI();
        } else if (BluetoothLeService.
                ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
            // Show all the supported services and characteristics on the
            // user interface.
            displayGattServices(mBluetoothLeService.getSupportedGattServices());
        } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
            displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
        }
    }
};


读取 BLE 属性


一旦你的程序连接上一个 GATT 服务器 并且发现了 services,就可以读、写它支持的属性. 例如, 下面片段迭代了服务器支持的所有 services 和 characteristics 并且将他们展示在了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);
         }
    ...
    }
...
}


接收 GATT 通知


    当设备的某个特定 characteristic 发生改变时 BLE 程序通常会要求得到通知. 如下片段展示了如何为某一 characteristic 设置通知, 通过使用setCharacteristicNotification()方法:

private BluetoothGatt mBluetoothGatt;
BluetoothGattCharacteristic characteristic;
boolean enabled;
...
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
...
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
        UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);

    一旦开启了某个 characteristic 的通知,那么当这个 characteristic 在远程设备上发生改变时会触发一个回调方法onCharacteristicChanged():

@Override
// Characteristic notification
public void onCharacteristicChanged(BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic) {
    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}


关闭客户端程序


    一旦你的程序使用BLE完毕, 它应该调用close()使系统适当的释放资源:

public void close() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.close();
    mBluetoothGatt = null;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值