zoukankan      html  css  js  c++  java
  • Bluetooth Low Energy——蓝牙低功耗

    Android4.3(API级别18)引入内置平台支持BLE的central角色,同时提供API和app应用程序用来发现设备,查询服务,和读/写characteristics。与传统蓝牙(ClassicBluetooth)不同,蓝牙低功耗(BLE)的目的是提供更显著的低功耗。这使得Android应用程序可以和具有低功耗的要求BLE设备,如接近传感器,心脏速率监视器,健身设备等进行通信。

     关键术语和概念

    下面是关键BLE术语和概念的总结:

    通用属性规范(GATT)—GATTprofile是一个通用规范用于在BLE链路发送和接收被称为“属性(attributes)”的数据片。目前所有的低功耗应用 profile都是基于GATT。

    蓝牙SIG定义了许多profile用于低功耗设备。Profile(配置文件)是一个规范,规范了设备如何工作在一个特定的应用场景。注意:一个设备可以实现多个profile。例如,一个设备可以包含一个心脏监测仪和电池电平检测器。

     属性协议( ATT )—GATT是建立在属性协议( ATT )的顶层,通常也被称为GATT/ ATT 。 ATT进行了优化用于在BLE设备上运行。为此,它采用尽可能少的字节越好。每个attribute属性被UUID(通用唯一标识符)唯一标识 ,UUID是标准128-bit格式的ID用来唯一标识信息。attributes 被 ATT 格式化characteristics和services形式进行传送。

    特征(Characteristics)— 一个characteristics包含一个单独的value值和0 –n个用来描述characteristic 值(value)的descriptors。一个characteristics可以被认为是一种类型的,类似于一个类。

    描述符(descriptor)—descriptor是被定义的attributes,用来描述一个characteristic的值。例如,一个descriptor可以指定一个人类可读的描述中,在可接受的范围里characteristic值,或者是测量单位,用来明确characteristic的值。

    服务(service)—service是characteristic的集合。例如,你可以有一个所谓的“Heart RateMonitor”service,其中包括characteristic,如“heart rate measurement ”。你可以在 bluetooth.org找到关于一系列基于GATT的profile和service。

     角色和职责

    以下是适用于当一个Android设备与BLE设备交互的角色和责任:

    中心设备(central)与外围设备(peripheral)。这也适用于BLE连接本身。Central设备进行扫描,寻找advertisenment,peripheral设备发出advertisement。

    GATT server(服务器)与GATTclient(客户端)。这决定了两个设备建立连接后如何互相交互。 

    要了解它们的区别,假设你有一个Android手机和活动跟踪器,活动跟踪器是一个BLE装置。这款手机扮演central角色;活动跟踪器扮演peripheral角色(建立一个BLE连接,必须具备两者。如果两个设备只支持central角色或peripheral角色,不能跟对方建立一个BLE连接)。

     一旦手机与活动跟踪器已经建立连接,他们开始相互传送GATT数据。根据它们传送数据的种类,其中一个可能作为 GATT server。例如,如果该活动跟踪器将传感器数据汇报到手机上,活动跟踪器作为server。如果活动跟踪器想要从手机接收更新,那么手机作为server。

     在本文档中使用的示例中,Android应用程序(在Android设备上运行)是GATT client。该应用从GATT server 获取数据,server是一款支持 HeartRate Profile的BLE心脏速率监测仪。但你可以设计​​你的Andr​​oid应用程序,作为GATT server角色。见BluetoothGattServer 获取更多信息。

    BLE权限

    为了使用应用程序中的蓝牙功能,你必须声明蓝牙权限BLUETOOTH。你需要这个权限执行任意蓝牙通讯,如请求连接,接受连接,传输数据。

    如果你希望你的应用程序启动设备发现或操纵蓝牙设置,还必须声明BLUETOOTH_ADMIN权限。注意:如果您使用BLUETOOTH_ADMIN权限,那么你还必须有BLUETOOTH权限。

     声明蓝牙权限在你的应用程序清单(manifest)文件。例如:

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

    如果你想声明,你的应用程序是只提供给BLE功能的设备,在您的应用程序的清单包括如下语句:

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

    不过,如果你想使你的应用程序提供给那些不支持BLE设备,你仍然应该在您的应用程序的清单包含这个上述语句,但设置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

    BluetoothAdapter是所有的蓝牙活动所必需的。该BluetoothAdapter代表设备自身的蓝牙适配器(蓝牙无线电)。只有一个蓝牙适配器用于整个系统,并且你的应用程序可以使用该对象进行交互。下面的代码片段显示了如何获取适配器。注意,该方法使用getSystemService() 返回BluetoothManager的一个实例, 用于获取适配器。Android 4.3 ( API级别18 )引入BluetoothManager

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

    2. 启用蓝牙

    接下来,你需要确保蓝牙已启用。用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作为参数。你必须实现这个回调,因为它决定扫描结果如何返回。因为扫描耗电量大,你应当遵守以下准则:

    1)只要你找到所需的设备,停止扫描。

    2)不要扫描一个循环,并设置您的扫描时间限制。以前可用的设备可能已经移出范围,继续扫描消耗电池电量。

    下面的代码片段显示了如何启动和停止扫描:

    复制代码
    /**
     * 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);
            }
            ...
        }
    ...
    }
    复制代码

    如何你想要扫描指定类型的peripheral设备,你可以用 startLeScan(UUID[],BluetoothAdapter.LeScanCallback)取代, 它提供了一组UUID对象用于说明你的应用程序支持的GATT services .

    下面是用于传送BLE扫描结果的接口函数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();
               }
           });
       }
    };
    复制代码

    注意:你可以只扫描BLE设备或传统蓝牙设备,就像Bluetooth描述那样。你不可以同时扫描BLE设备和传统蓝牙设备。

    连接到GATT server

    在和一个BLE设备交互的第一步是连接到它,更具体地,连接到所述装置上的GATT server。要连接到一个BLE设备上的GATT server,您可以使用connectGatt()方法。这个方法有三个参数:一个Context 对象,一个autoConnect(布尔值,表示是否自动连接到BLE装置)和BluetoothGattCallback:

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

    这个函数连接到由BLE设备上的GATTserver,并返回一个BluetoothGatt实例,您可以使用它来进行GATT client操作。调用者(Android应用程序)是GATT client。该BluetoothGattCallback是用来提供结果给client,如连接状态,以及任何进一步的GATT client操作。

    在这个例子中,BLE应用程序提供了一个活动(DeviceControlActivity)连接,显示数据,和显示该设备支持的GATTservices和characteristics。根据用户的输入,这一活动与一个叫BluetoothLeService的Service交互,BluetoothService通过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()辅助方法并传递给它一个动作。注意,在该部分中的数据解析参照 Bluetooth Heart Rate Measurement profilespecifications进行。

    复制代码
    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) + "
    " +
                        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属性

    一旦你的Android应用程序已连接到GATTserver和discoveriable service,它可以读取和写入支持的attributes。例如,通过server的service和characteristic这个片段进行迭代,在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通知 (Notifications)

    这是常见的设备上的BLE应用程序要求被通知,当一个特定characteristic改变时。这段代码显示了如何使用 setCharacteristicNotification()方法设置一个Notifications用于characteristic:

    复制代码
    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);
    复制代码

    一旦启动了属性通知( notifications for acharacteristic),如果在远程设备上characteristic 发生改变,onCharacteristicChanged() 回调函数将被启动。

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

    关闭Client应用程序

    一旦你的应用程序已经使用BLE装置完成后,应该调用close(),这样系统就可以适当地释放资源:

    复制代码
    public void close() {
        if (mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.close();
        mBluetoothGatt = null;
    }
    复制代码
  • 相关阅读:
    LeetCode 242. Valid Anagram (验证变位词)
    LeetCode 205. Isomorphic Strings (同构字符串)
    LeetCode 204. Count Primes (质数的个数)
    LeetCode 202. Happy Number (快乐数字)
    LeetCode 170. Two Sum III
    LeetCode 136. Single Number (落单的数)
    LeetCode 697. Degree of an Array (数组的度)
    LeetCode 695. Max Area of Island (岛的最大区域)
    Spark中的键值对操作
    各种排序算法总结
  • 原文地址:https://www.cnblogs.com/xiaorenwu702/p/4304398.html
Copyright © 2011-2022 走看看