Android笔记系列--(2) Bluetooth BLE低功耗

上篇 :Android笔记系列--(1)传统蓝牙 socket通讯方式

整理了一下网上关于蓝牙的资料,几乎涵盖Android开发使用蓝牙的方方面面。以下有部分内容引用的是来自网上的博客,文末将添加这些引用的来源,如果填漏了可以告诉我。


本文练习源码:https://github.com/StarsAaron/BluetoothTestDemo

主要内容:

(1)BLE蓝牙的基础概念

(2)Android 各版本API的变化

(3)设备作为中心和外围模式的使用

(4)返回数据解析


蓝牙主从关系:
蓝牙通信时一个为主设备一个为从设备。
一个蓝牙设备以主模式发起呼叫时,需要知道对方的蓝牙地址,配对密码等信息,配对完成后,可直接发起呼叫。 
一个蓝牙主端设备,可同时与7 个蓝牙从端设备进行通讯。
一个具备蓝牙通讯功能的设备,可以在两个角色间切换,平时工作在从模式,等待其它主设备来连接,需要时,转换为主模式,向其它设备发起呼叫。 


蓝牙分为三种:

(1)Bluetooth Smart Ready(双模) 
适用于任何双模蓝牙4.0的电子产品,可与Bluetooth Smart及标准蓝牙相通.
(2)Bluetooth Smart(Smart是低功耗蓝牙的标识) 
应用在心率监视器或计步器等使用扭扣式电池并传输单一的装置
(3)标准 Bluetooth 

标准蓝牙则无法与Bluetooth Smart相通


Android蓝牙开发基础了解

SIG在Bluetooth  4.0 版本规定了Bluetooth Smart及Bluetooth Smart Ready。

BLE设备分单模和双模两种,双模简称BR(容易跟基础速率的简称混淆),商标为Bluetooth Smart Ready,单模简称BLE或者LE,商标为Bluetooth Smart。

Android是在4.3后才支持BLE,这说明不是所有蓝牙手机都支持BLE,但也并不是4.3之后的手机都可以使用BLE,还取决于手机硬件蓝牙模块的版本是否具有Ble功能。

支持BLE的蓝牙手机一般是双模的。双模兼容传统蓝牙,可以和传统蓝牙通信,也可以和BLE通信,常用在手机上,android4.3和IOS4.0之后版本都支持BR,也就是双模设备。
单模只能和BR和单模的设备通信,不能和传统蓝牙通信

Android4.3只支持中心设备模式(Central),直到2014.6.26 Android Lollipop之后才支持周边设备模式(Peripheral),在Android 5.0 API 21 android.bluetooth.le包下,新增加 Scaner相关类和 Advertiser 相关类。
先看下 android.bluetooth.le 包下内容:

1. Android BLE 周边设备 (Peripheral)可以通过 Advertiser 相关类实现操作; 
2. Android BLE 中心设备 (Central)可以通过 Scanner相关类实现蓝牙扫描; 
3. Android BLE 建立中心连接的时候,使用 BlueToothDevice#connectGatt() 实现


Central 发现并连接广播中的 Peripheral

在BLE中 ,Peripheral 通过广播的方式向Central提供数据是主要方式。
主要操作如下:
服务端 外围设备(Peripheral): 向外广播数据包(Advertising)形式的数据,比如设备名称,功能等!
客户端 中心设备(Central ) : 可以通过实现扫描和监听到广播数据包,并展示数据。
如图所示 :


外围设备(Peripheral )数据如何构成
Peripheral 包含一个或者多个Service(服务)以及有关其连接信号强度的有用信息 。
Service :是指为了实现一个功能或者特性的数据采集和相关行为的集合,包含一个或多个特征(Characteristic)。
Characteristic :它包括一个value和0至多个对次value的描述(Descriptor),知道更多数据细节。
Descriptor : 对Characteristic的描述,例如范围、计量单位等
如图所示:


来源:  http://blog.csdn.net/LABLENET/article/details/54924157


Android API版本变化:

传统蓝牙搜索:

// 设置广播信息过滤
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
intentFilter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
// 注册广播接收器,接收并处理搜索结果,在onDestory中记得取消注册
registerReceiver(receiver, intentFilter);
// 寻找蓝牙设备,android会将查找到的设备以广播形式发出去
// startDiscovery 方法可以查找到传统蓝牙和Ble设备但是startDiscovery的回调无法返回
// Ble的广播,所以无法通过广播识别设备,且startDiscovery扫描Ble的效率比StartLeScan低很多。
mBluetoothAdapter.startDiscovery();
//4.3以下的通过广播方式来搜索
private BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            Log.i("BroadcastReceiver--", device.getName());
        }
    }
};
4.3 新增的Ble蓝牙搜索接口:
// 4.3(API 18) 蓝牙搜索使用下面
mBluetoothAdapter.startLeScan(mLeScanCallback);
/**
 * 旧回调,参数scanRecord是数据的字节数组,不够直观,要手动解析数据
 */
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi, final byte[] scanRecord) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mLeDeviceList.addDevice(device);
                Toast.makeText(MainActivity.this, device.getName() + device.getAddress(), Toast.LENGTH_SHORT).show();
                Log.i(TAG, device.getName() + device.getAddress());
                //这里的参数 scanRecord 就是广播数据,这里同时包含 广播数据 和 扫描响应数据 (如果有的话),所以长度一般就是 62 字节。
                parseData(scanRecord);
            }
        });
    }
};

5.0 新蓝牙搜索接口:
//5.0 之后使用android.bluetooth.le 包新接口
BluetoothLeScanner bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
bluetoothLeScanner.startScan(scanCallback);
/**
 * 新添加的android.bluetooth.le 包新接口回调
 */
private ScanCallback scanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, final ScanResult result) {
        super.onScanResult(callbackType, result);
        BluetoothDevice device = result.getDevice();
        mLeDeviceList.addDevice(device);
        Log.d(TAG, "Device name: " + device.getName());
        Log.d(TAG, "Device address: " + device.getAddress());
        Log.d(TAG, "Device service UUIDs: " + device.getUuids());
        ScanRecord record = result.getScanRecord();
        Log.d(TAG, "Record advertise flags: 0x" + Integer.toHexString(record.getAdvertiseFlags()));
        Log.d(TAG, "Record Tx power level: " + record.getTxPowerLevel());
        Log.d(TAG, "Record device name: " + record.getDeviceName());
        Log.d(TAG, "Record service UUIDs: " + record.getServiceUuids());
        Log.d(TAG, "Record service data: " + record.getServiceData());
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
            }
        });
    }
    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        super.onBatchScanResults(results);
    }
    @Override
    public void onScanFailed(int errorCode) {
        super.onScanFailed(errorCode);
    }
};


基本概念
1、Generic Access Profile(GAP)

用来控制设备连接和广播,GAP使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。
2、Generic Attribute Profile(GATT)
通过BLE连接,读写属性类数据的Profile通用规范,现在所有的BLE应用Profile都是基于GATT的。
3、Attribute Protocol (ATT)
GATT是基于ATTProtocol的,ATT针对BLE设备做了专门的优化,具体就是在传输过程中使用尽量少的数据,每个属性都有一个唯一的UUID,属性将以characteristics and services的形式传输。
4、Characteristic
Characteristic可以理解为一个数据类型,它包括一个value和0至多个对次value的描述(Descriptor)。
5、Descriptor
对Characteristic的描述,例如范围、计量单位等。
6、Service
Characteristic的集合。例如一个service叫做“Heart Rate Monitor”,它可能包含多个Characteristics,其中可能包含一个叫做“heart ratemeasurement”的Characteristic。
7、UUID
唯一标示符,每个Service,Characteristic,Descriptor,都是由一个UUID定义。“GATT层”中定义的所有属性都有一个UUID值,UUID是全球唯一的128位的号码,它用来识别不同的特性。

蓝牙技术联盟 UUID
蓝牙核心规范制定了两种不同的UUID,一种是基本的UUID,一种是代替基本UUID的16位UUID。
所有的蓝牙技术联盟定义UUID共用了一个基本的UUID:
0x0000xxxx-0000-1000-8000-00805F9B34FB
为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,以代替上面的基本UUID的‘x’部分。例如,心率测量特性使用0X2A37作为它的16位UUID,因此它完整的128位UUID为:
0x00002A37-0000-1000-8000-00805F9B34FB
虽然技术联盟使用相同的基本UUID,但是16位的UUID足够唯一地识别蓝牙技术联盟所定义的各种属性。
蓝牙技术联盟所用的基本UUID不能用于任何定制的属性、服务和特性。对于定制的属性,必须使用另外完整的128位UUID。

供应商特定的UUID
SoftDevice 根据蓝牙技术联盟定义UUID类似的方式定义UUID:先增加一个特定的基本UUID,再定义一个16位的UUID(类似于一个别名),再加载在基本UUID之上。这种采用为所有的定制属性定义一个共用的基本UUID的方式使得应用变为更加简单,至少在同一服务中更是如此。
使用软件nRFgo Studio非常容易产生一个新的基本UUID,见第29页第4.4.3节“服务初始化”。
例如,在LED BUTTON示例中,采用0x0000xxxx-1212-EFDE-1523-785FEABCD123作为基本UUID。

蓝牙核心规范没有任何规则或是建议如何对加入基本UUID的16位UUID进行分配,因此你可以按照你的意图来任意分配。

例如,在LED BUTTON示例中,0x1523作为服务的UUID,0x1524作为LED特性的UUID,0x1525作为按键状态特性的UUID。

8、profile(数据配置文件)
一个profile文件可以包含一个或者多个服务,一个profile文件包含需要的服务的信息或者为对等设备如何交互的配置文件的选项信息。设备的GAP和GATT的角色都可能在数据的交换过程中改变,因此,这个文件应该包含广播的种类、所使用的连接间隔、所需的安全等级等信息。
       需要注意的是一个profile中的属性表不能包含另一个属性表。
在LED BUTTON示例中的profile不是一个标准描述的profile。


Android BLE API

1、BluetoothGatt
继承BluetoothProfile,通过BluetoothGatt可以连接设备(connect),发现服务(discoverServices),并把相应地属性返回到BluetoothGattCallback,可以看成蓝牙设备从连接到断开的生命周期。
2、BluetoothGattCharacteristic
相当于一个数据类型,可以看成一个特征或能力,它包括一个value和0~n个value的描述(BluetoothGattDescriptor)。
3、BluetoothGattDescriptor
描述符,对Characteristic的描述,包括范围、计量单位等。
4、BluetoothGattService
服务,Characteristic的集合。
5、BluetoothProfile
一个通用的规范,按照这个规范来收发数据。
6、BluetoothManager
通过BluetoothManager来获取BluetoothAdapter。 
BluetoothManager bluetoothManager =(BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
7、BluetoothAdapter
代表了移动设备的本地的蓝牙适配器, 通过该蓝牙适配器可以对蓝牙进行基本操作,一个Android系统只有一个BluetoothAdapter,通过BluetoothManager获取。 
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
8、BluetoothDevice
扫描后发现可连接的设备,获取已经连接的设备。 
BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);
9、BluetoothGattCallback
已经连接上设备,对设备的某些操作后返回的结果。


中心设备模式操作流程

权限声明:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
如果您要声明自己的应用只适用于支持BLE的设备,请在应用清单中包含以下内容:
<uses-feature android:name="android.bluetooth.le" android:required="true"/>

Android 5.0之前是android.hardware.bluetooth_le

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

不过,如果您想让应用程式适用于不支援BLE的装置,您仍应在应用的清单中加入这个元素,但required="false"设为required="false" 。

然后在运行时,您可以通过使用PackageManager.hasSystemFeature()确定BLE可用性:
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
     Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
     finish();
 }
android 6.0 以上的设备,获取蓝牙信息还需要定位权限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

1、蓝牙开启

在使用蓝牙BLE之前,需要确认Android设备是否支持BLE feature(required为false时),另外要需要确认蓝牙是否打开。如果发现不支持BLE,则不能使用BLE相关的功能;如果支持BLE,但是蓝牙没打开,则需要打开蓝牙。代码示例如下:
//是否支持蓝牙模块
@TargetApi(18)
public static boolean isSupportBle(Context context) {
    if(context != null && context.getPackageManager().hasSystemFeature("android.hardware.bluetooth_le")) {
        BluetoothManager manager = (BluetoothManager)context.getSystemService("bluetooth");
        return manager.getAdapter() != null;
    } else {
        return false;
    }
}
//是否开启蓝牙
@TargetApi(18)
public static boolean isBleEnable(Context context) {
    if(!isSupportBle(context)) {
        return false;
    } else {
        BluetoothManager manager = (BluetoothManager)context.getSystemService("bluetooth");
        return manager.getAdapter().isEnabled();
    }
}
//开启蓝牙
public static void enableBle(Activity act, int requestCode) {
    Intent mIntent = new Intent("android.bluetooth.adapter.action.REQUEST_ENABLE");
    act.startActivityForResult(mIntent, requestCode);
}
//蓝牙开启过程
if(isSupportBle(mContext)){
    //支持蓝牙模块
    if(!isBleEnable(mContext)){
        //没开启蓝牙则开启
        enableBle(mSelfActivity, 1);
    }
} else{
    //不支持蓝牙模块处理
}
//蓝牙开启回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    //判断requestCode是否为开启蓝牙时传进去的值,再做相应处理
    if(requestCode == 1){
        //蓝牙开启成功后的处理
    }
    super.onActivityResult(requestCode, resultCode, data);
}
2、设备搜索
BluetoothAdapter.startDiscovery在大多数手机上是可以同时发现经典蓝牙和Ble的,但是startDiscovery的回调无法返回Ble的广播,所以无法通过广播识别设备,且startDiscovery扫描Ble的效率比StartLeScan低很多。所以在实际应用中,还是StartDiscovery和StartLeScan分开扫,前者扫传统蓝牙,后者扫低功耗蓝牙。
由于搜索需要尽量减少功耗,因此在实际使用时需要注意:当找到对应的设备后,立即停止扫描;不要循环搜索设备,为每次搜索设置适合的时间限制,避免设备不在可用范围的时候持续不停扫描,消耗电量。
通过调用BluetoothAdapter的 startLeScan() 搜索BLE设备。调用此方法时需要传入 BluetoothAdapter.LeScanCallback 参数。具体代码示例如下:
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
bluetoothAdapter.startLeScan(new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        //对扫描到的设备进行处理,可以依据BluetoothDevice中的信息、信号强度rssi以及广播包和响应包组成的scanRecord字节数组进行分析
        parseData(scanRecord);
    }
});
​
//解析数据
public static ParsedAd parseData(byte[] adv_data) {  
    ParsedAd parsedAd = new ParsedAd();
    ByteBuffer buffer = ByteBuffer.wrap(adv_data).order(ByteOrder.LITTLE_ENDIAN);
    while (buffer.remaining() > 2) {
        byte length = buffer.get();
        if (length == 0)
            break;
        byte type = buffer.get();
        length -= 1;
        switch (type) {
            case 0x01: // Flags
                parsedAd.flags = buffer.get();
                length--;
                break;
            case 0x02: // Partial list of 16-bit UUIDs
            case 0x03: // Complete list of 16-bit UUIDs
            case 0x14: // List of 16-bit Service Solicitation UUIDs
                while (length >= 2) {
                    parsedAd.uuids.add(UUID.fromString(String.format(
                            "%08x-0000-1000-8000-00805f9b34fb", buffer.getShort())));
                    length -= 2;
                }
                break;
            case 0x04: // Partial list of 32 bit service UUIDs
            case 0x05: // Complete list of 32 bit service UUIDs
                while (length >= 4) {
                    parsedAd.uuids.add(UUID.fromString(String.format(
                            "%08x-0000-1000-8000-00805f9b34fb", buffer.getInt())));
                    length -= 4;
                }
                break;
            case 0x06: // Partial list of 128-bit UUIDs
            case 0x07: // Complete list of 128-bit UUIDs
            case 0x15: // List of 128-bit Service Solicitation UUIDs
                while (length >= 16) {
                    long lsb = buffer.getLong();
                    long msb = buffer.getLong();
                    parsedAd.uuids.add(new UUID(msb, lsb));
                    length -= 16;
                }
                break;
            case 0x08: // Short local device name
            case 0x09: // Complete local device name
                byte sb[] = new byte[length];
                buffer.get(sb, 0, length);
                length = 0;
                parsedAd.localName = new String(sb).trim();
                break;              
            case (byte) 0xFF: // Manufacturer Specific Data
                parsedAd.manufacturer = buffer.getShort();
                length -= 2;
                break;
            default: // skip
                break;
        }
        if (length > 0) {
            buffer.position(buffer.position() + length);
        }
    }
    return parsedAd;
}
3、设备通信
两个设备通过BLE通信,首先需要建立GATT连接,这里我们讲的是Android设备作为client端,连接GATT Server。连接GATT Server,需要调用BluetoothDevice的connectGatt()方法,此函数带三个参数:Context、autoConnect(boolean)和 BluetoothGattCallback 对象。调用后返回BluetoothGatt对象,它是GATT profile的封装,通过这个对象,我们就能进行GATT Client端的相关操作。如断开连接bluetoothGatt.disconnect(),发现服务bluetoothGatt.discoverServices()等等。示例代码如下:
BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(address);
BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback(){
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        super.onConnectionStateChange(gatt, status, newState);
        String intentAction;
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            intentAction = ACTION_GATT_CONNECTED;
            mConnectionState = STATE_CONNECTED;
            //broadcastUpdate(intentAction);//发送状态
            Log.d("连接", "Connected to GATT server.");
            // Attempts to discover services after successful connection.
            Log.d("", "Attempting to start service discovery:" +
            mBluetoothGatt.discoverServices());//
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            intentAction = ACTION_GATT_DISCONNECTED;
            mConnectionState = STATE_DISCONNECTED;
            Log.i("", "Disconnected from GATT server.");
            // broadcastUpdate(intentAction);
        }
    }
​
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        if (status == BluetoothGatt.GATT_SUCCESS) {
            // broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            Log.e("discovered1", "onServicesDiscovered received: " +status);
            BlueServiceList=gatt.getServices();
            for(BluetoothGattService BS:BlueServiceList){
                Log.e("服务",BS.getUuid().toString());
                if(n==0){
                    gattDevice=gatt;
                    BluetoothGattService gattS=gattDevice.getService(UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"));
                    if(gattS!=null){
                        characteristic=gattS.getCharacteristic(UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb"));
                        gattDevice.setCharacteristicNotification(characteristic, true);
                        n++;
                    }
                }
            }
         }else{
            //Log.e("discovered0", "onServicesDiscovered received: " +status);
         }
    }
​
    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicRead(gatt, characteristic, status);
        if (status == BluetoothGatt.GATT_SUCCESS) {
            // broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            byte[] data = characteristic.getValue();
            Log.e("onCharacteristicRead", "onCharacteristicRead received: " +
            new String(data));
            //gatt.readCharacteristic(characteristic);
        }
    }
​
    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicWrite(gatt, characteristic, status);
    }
​
    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicChanged(gatt, characteristic);
        byte[] data = characteristic.getValue();
        Log.e("onCharacteristicChanged", "onCharacteristicChangedreceived: " + new String(data));
    }
​
    @Override
    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        super.onDescriptorRead(gatt, descriptor, status);
    }
​
    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        super.onDescriptorWrite(gatt, descriptor, status);
    }
​
    @Override
    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
        super.onReliableWriteCompleted(gatt, status);
    }
​
    @Override
    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
        super.onReadRemoteRssi(gatt, rssi, status);
    }
​
    @Override
    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
        super.onMtuChanged(gatt, mtu, status);
    }
};
​
BluetoothGatt bluetoothGatt = bluetoothDevice.connectGatt(this, false, bluetoothGattCallback);
​
//以下为获得Gatt后的相关操作对应的响应方法
//notification to onCharacteristicChanged;
bluetoothGatt.setCharacteristicNotification(characteristic, true);
​
//readCharacteristic to onCharacteristicRead;
bluetoothGatt.readCharacteristic(characteristic);
​
//writeCharacteristic to onCharacteristicWrite;
bluetoothGatt.wirteCharacteristic(mCurrentcharacteristic);
​
//connect and disconnect to onConnectionStateChange;
bluetoothGatt.connect();
bluetoothGatt.disconnect();
​
//readDescriptor to onDescriptorRead;
bluetoothGatt.readDescriptor(descriptor);
​
//writeDescriptor to onDescriptorWrite;
bluetoothGatt.writeDescriptor(descriptor);
​
//readRemoteRssi to onReadRemoteRssi;
bluetoothGatt.readRemoteRssi();
​
//executeReliableWrite to onReliableWriteCompleted;
bluetoothGatt.executeReliableWrite();
​
//获得某个 Gatt 外设提供的服务
bluetoothGatt.discoverServices();
​
//获得一个外设的所有服务, 存在一个 arraylist<>中
bluetoothGatt.getServices()
​
//通过UUID获得某个服务
BluetoothGattService gattS =gattDevice.getService(UUID uuid);
​
//通过UUID获得某个外设某个服务的一个Characteristic
characteristic = gattS.getCharacteristic(UUID uuid);
​
//设置当characteristic改变时通知,用来从外设获得发来的值
gatt.setCharacteristicNotification(characteristic, true);
​
//设备发来数据时回调函数
onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic)
​
//发送数据到设备
characteristic.setValue("0123");
gatt.writeCharacteristic(characteristic);

四、数据解析

BLE中有两种角色Central和Peripheral,也就是中心设备和外围设备,中心设备可以主动连接外围设备,外围设备发送广播或者被中心设备连接,外围通过广播被中心设备发现,广播中带有外围设备自身的相关信息。
数据包有两种:广播包(Advertising Data)和响应包(Scan Response),其中广播包是每个设备必须广播的,而响应包是可选的。数据包的格式如下图所示(图片来自官方 Spec): 


每个包都是 31 字节,数据包中分为有效数据(significant)和无效数据(non-significant)两部分。
有效数据部分:包含若干个广播数据单元,称为AD Structure。如图中所示,AD Structure的组成是:第一个字节是长度值Len,表示接下来的Len个字节是数据部分。数据部分的第一个字节表示数据的类型AD Type,剩下的Len - 1个字节是真正的数据AD data。其中AD type非常关键,决定了AD Data的数据代表的是什么和怎么解析,这个在后面会详细讲;
无效数据部分:因为广播包的长度必须是31字节,如果有效数据部分不到31字节,剩下的就用0补齐,这部分的数据是无效的,解析的时候,直接忽略即可。
查看Nordic的SDK中的定义,AD type的定义在程序的“ble_gap.h”头文件中。定义如下:
#define BLE_GAP_AD_TYPE_FLAGS                               0x01 //< Flags for discoverability. 
#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE   0x02 //< Partial list of 16 bit service UUIDs. 
#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE         0x03 //< Complete list of 16 bit service UUIDs.  
#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE   0x04 //< Partial list of 32 bit service UUIDs.  
#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE         0x05 //< Complete list of 32 bit service UUIDs. 
#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE  0x06 //< Partial list of 128 bit service UUIDs.  
#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE        0x07 //< Complete list of 128 bit service UUIDs. 
#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME                    0x08 //< Short local device name. 
#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME                 0x09 //< Complete local device name. 
#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL                      0x0A //< Transmit power level. 
#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE                     0x0D //< Class of device. 
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C               0x0E //< Simple Pairing Hash C. 
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R         0x0F //< Simple Pairing Randomizer R. 
#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE           0x10 //< Security Manager TK Value. 
#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS          0x11 //< Security Manager Out Of Band Flags. 
#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE     0x12 //< Slave Connection Interval Range. 
#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT       0x14 //< List of 16-bit Service Solicitation UUIDs. 
#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT      0x15 //< List of 128-bit Service Solicitation UUIDs. 
#define BLE_GAP_AD_TYPE_SERVICE_DATA                        0x16 //< Service Data - 16-bit UUID. 
#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS               0x17 //< Public Target Address. 
#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS               0x18 //< Random Target Address. 
#define BLE_GAP_AD_TYPE_APPEARANCE                          0x19 //< Appearance. 
#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL                0x1A //< Advertising Interval.  
#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS         0x1B //< LE Bluetooth Device Address. 
#define BLE_GAP_AD_TYPE_LE_ROLE                             0x1C //< LE Role. 
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256            0x1D //< Simple Pairing Hash C-256. 
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256      0x1E //< Simple Pairing Randomizer R-256. 
#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID             0x20 //< Service Data - 32-bit UUID. 
#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID            0x21 //< Service Data - 128-bit UUID. 
#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA                 0x3D //< 3D Information Data.
#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA          0xFF //< Manufacturer Specific Data. 

所有的 AD Type 的定义在文档Core Specification Supplement中。根据上面头文件中的定义,AD Type包括如下类型:
1、TYPE = 0x01:标识设备LE物理连接的功能,占一个字节,各bit为1时定义如下:
bit 0: LE有限发现模式
bit 1: LE普通发现模式
bit 2: 不支持BR/EDR
bit 3: 对Same Device Capable(Controller)同时支持BLE和BR/EDR
bit 4: 对Same Device Capable(Host)同时支持BLE和BR/EDR
bit 5..7: 预留
2、TYPE = 0x02:非完整的16 bit UUID列表 
3、TYPE = 0x03:完整的16 bit UUID列表 
4、TYPE = 0x04:非完整的32 bit UUID列表 
5、TYPE = 0x05:完整的32 bit UUID列表 
6、TYPE = 0x06:非完整的128 bit UUID列表 
7、TYPE = 0x07:完整的128 bit UUID列表 
8、TYPE = 0x08:设备简称 
9、TYPE = 0x09:设备全名 
10、TYPE = 0x0A:表示设备发送广播包的信号强度 
11、TYPE = 0x0D:设备类别 
12、TYPE = 0x0E:设备配对的Hash值 
13、TYPE = 0x0F:设备配对的随机值 
14、TYPE = 0x10:TK安全管理(Security Manager TK Value) 
15、TYPE = 0x11:带外安全管理(Security Manager Out of Band),各bit定义如下:
bit 0: OOB Flag,0-表示没有OOB数据,1-表示有
bit 1: 支持LE
bit 2: 对Same Device Capable(Host)同时支持BLE和BR/EDR
bit 3: 地址类型,0-表示公开地址,1-表示随机地址
16、TYPE = 0x12:外设(Slave)连接间隔范围,数据中定义了Slave最大和最小连接间隔,数据包含4个字节:前两字节定义最小连接间隔,取值范围:0x0006 ~ 0x0C80,而0xFFFF表示未定义;后两字节,定义最大连接间隔,取值范围同上,不过需要保证最大连接间隔大于或者等于最小连接间隔。 
17、TYPE = 0x14:服务搜寻16 bit UUID列表 
18、TYPE = 0x15:服务搜寻128 bit UUID列表 
19、TYPE = 0x16:16 bit UUID Service,前两个字节是UUID,后面是Service的数据 
20、TYPE = 0x17:公开目标地址,表示希望这个广播包被指定的目标设备处理,此设备绑定了公开地址 
21、TYPE = 0x18:随机目标地址,表示希望这个广播包被指定的目标设备处理,此设备绑定了随机地址 
22、TYPE = 0x19:表示设备的外观 
23、TYPE = 0x1A:广播区间 
24、TYPE = 0x1B:LE设备地址 
25、TYPE = 0x1C:LE设备角色 
26、TYPE = 0x1D:256位设备配对的Hash值 
27、TYPE = 0x1E:256位设备配对的随机值 
28、TYPE = 0x20:32 bit UUID Service,前4个字节是UUID,后面是Service的数据 
29、TYPE = 0x21:128 bit UUID Service,前16个字节是UUID,后面是Service的数据 
30、TYPE = 0x3D:3D信息数据 
31、TYPE = 0xFF:厂商自定义数据,厂商自定义的数据中,前两个字节表示厂商ID,剩下的是厂商自己按照需求添加,里面的数据内容自己定义。
根据如下数据包,举例说明解析的思路 
搜索设备获取的数据包如下:
02 01 06 14 FF 11 22 00 00 00 01 00 1F 09 01 00 00 00 CE DD 5E 5A 5D 23 06 08 48 45 54 2D 35 09 03 E7 FE 12 FF 0F 18 0A 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
根据解析规则,可分成如下部分: 
1、广播数据
02 01 06 14 FF 11 22 00 00 00 01 00 1F 09 01 00 00 00 CE DD 5E 5A 5D 23 06 08 48 45 54 2D 35
2、响应数据
09 03 E7 FE 12 FF 0F 18 0A 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
3、有效数据
02 01 06 14 FF 11 22 00 00 00 01 00 1F 09 01 00 00 00 CE DD 5E 5A 5D 23 06 08 48 45 54 2D 35 09 03 E7 FE 12 FF 0F 18 0A 18
4、无效数据
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
其中的有效数据又可分为如下几个数据单元: 
02 01 06 
14 FF 11 22 00 00 00 01 00 1F 09 01 00 00 00 CE DD 5E 5A 5D 23 
06 08 48 45 54 2D 35 
09 03 E7 FE 12 FF 0F 18 0A 18 
根据上面定义的AD Type分别解析如下: 
第一组数据告诉我们该设备属于LE普通发现模式,不支持BR/EDR; 
第二组数据告诉我们该数据为厂商自定义数据,一般是必须解析的,可根据协议规则进行解析获取对应的所需信息; 
第三组数据告诉我们该设备的简称为HET-5,其中对应的字符是查找ASSIC表得出的; 
第四组数据告诉我们UUID为E7FE-12FF-0F18-0A18.


五、参考链接

1、蓝牙Bluetooth BR/EDR 和 Bluetooth Smart 必需要知道的十个不同点 
2、BLE简介和Android BLE编程 
3、BLE广播数据解析

来源:  http://blog.csdn.net/xiaoyaoyou1212/article/details/51854454


外围设备模式

初始化BLE蓝牙广播(广告)

(1)广播的设置
(2)设置广播的数据
(3)设置响应的数据

(4)设置连接回调

private void initAdvertiser() {
    BluetoothLeAdvertiser advertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
    if(advertiser == null){
        Toast.makeText(this,"不支持周边服务模式",Toast.LENGTH_SHORT).show();
        return;
    }
    // 广播设置
    AdvertiseSettings settings = new AdvertiseSettings.Builder()
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
            .setConnectable(false).build();
    ParcelUuid pUuid = new ParcelUuid(UUID.fromString("00001000-0000-1000-8000-00805f9b34fb"));
    // 广播数据
    AdvertiseData data = new AdvertiseData.Builder()
            .setIncludeDeviceName(true)
            .addServiceUuid(pUuid)
            .addServiceData(pUuid,
                    "Data".getBytes(Charset.forName("UTF-8"))).build();
    // 发出广播数据
    advertiser.startAdvertising(settings, data, mAdvertiseCallback);
}

在被BLE设备连接后,将触发 AdvertiseCallback 的 onStartSuccess,我们在这之后,初始化GATT的服务。
/**
 * 广播回调
 */
final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
    @Override
    public void onStartSuccess(AdvertiseSettings settingsInEffect) {
        super.onStartSuccess(settingsInEffect);
        if (settingsInEffect != null) {
            Log.d(TAG, "onStartSuccess TxPowerLv=" + settingsInEffect.getTxPowerLevel() + " mode=" + settingsInEffect.getMode()
                    + " timeout=" + settingsInEffect.getTimeout());
        } else {
            Log.e(TAG, "onStartSuccess, settingInEffect is null");
        }
        Log.e(TAG, "onStartSuccess settingsInEffect" + settingsInEffect);
    }
    @Override
    public void onStartFailure(int errorCode) {
        super.onStartFailure(errorCode);
        if (errorCode == ADVERTISE_FAILED_DATA_TOO_LARGE) {
            Toast.makeText(MainActivity.this, "Operation failed due to an internal error", Toast.LENGTH_LONG).show();
            Log.e(TAG, "Failed to start advertising as the advertise data to be broadcasted is larger than 31 bytes.");
        } else if (errorCode == ADVERTISE_FAILED_TOO_MANY_ADVERTISERS) {
            Toast.makeText(MainActivity.this, "Operation failed due to an internal error", Toast.LENGTH_LONG).show();
            Log.e(TAG, "Failed to start advertising because no advertising instance is available.");
        } else if (errorCode == ADVERTISE_FAILED_ALREADY_STARTED) {
            Toast.makeText(MainActivity.this, "Operation failed due to an internal error", Toast.LENGTH_LONG).show();
            Log.e(TAG, "Failed to start advertising as the advertising is already started");
        } else if (errorCode == ADVERTISE_FAILED_INTERNAL_ERROR) {
            Toast.makeText(MainActivity.this, "Operation failed due to an internal error", Toast.LENGTH_LONG).show();
            Log.e(TAG, "Operation failed due to an internal error");
        } else if (errorCode == ADVERTISE_FAILED_FEATURE_UNSUPPORTED) {
            Toast.makeText(MainActivity.this, "This feature is not supported on this platform", Toast.LENGTH_LONG).show();
            Log.e(TAG, "This feature is not supported on this platform");
        } else {
            Toast.makeText(MainActivity.this, "onStartFailure errorCode", Toast.LENGTH_LONG).show();
            Log.e(TAG, "onStartFailure errorCode" + errorCode);
        }
    }
};
初始化GATT的服务

(1) 通过 mBluetoothManager.openGattServer() 获得 bluetoothGattServer
(2) 添加 服务,特征,描述。这些内容要让客户端知道。
private void initServices(Context context) {
    bluetoothGattServer = mBluetoothManager.openGattServer(context, bluetoothGattServerCallback);
    BluetoothGattService service = new BluetoothGattService(UUID_SERVER,
            BluetoothGattService.SERVICE_TYPE_PRIMARY);
    //add a read characteristic.
    BluetoothGattCharacteristic characteristicRead = new BluetoothGattCharacteristic(UUID_CHARREAD,
            BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ);
    //add a descriptor
    BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,
            BluetoothGattCharacteristic.PERMISSION_WRITE);
    characteristicRead.addDescriptor(descriptor);
    service.addCharacteristic(characteristicRead);
    //add a write characteristic.
    BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(UUID_CHARWRITE,
            BluetoothGattCharacteristic.PROPERTY_WRITE |
                    BluetoothGattCharacteristic.PROPERTY_READ |
                    BluetoothGattCharacteristic.PROPERTY_NOTIFY,
            BluetoothGattCharacteristic.PERMISSION_WRITE);
    service.addCharacteristic(characteristicWrite);
    bluetoothGattServer.addService(service);
    Log.e(TAG, "2. initServices ok");
}
在 openGattServer 方法中,我们需要传入回调
bluetoothGattServer = mBluetoothManager.openGattServer(context, bluetoothGattServerCallback);


配置数据交互回调

回调时间有:连接状态变化,收发消息,通知消息
private BluetoothGattServerCallback bluetoothGattServerCallback = new BluetoothGattServerCallback() {
    /**
     * 1.连接状态发生变化时
     */
    @Override
    public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
        super.onConnectionStateChange(device, status, newState);
    }
    @Override
    public void onServiceAdded(int status, BluetoothGattService service) {
        super.onServiceAdded(status, service);
    }
    @Override
    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset
            , BluetoothGattCharacteristic characteristic) {
        
          bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset
                , characteristic.getValue());
          super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
    }
    /**
     * 3. onCharacteristicWriteRequest,接收具体的字节
     */
    @Override
    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId
            , BluetoothGattCharacteristic characteristic, boolean preparedWrite
            , boolean responseNeeded, int offset, byte[] requestBytes) {
        
          bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS
                , offset, requestBytes);
        
          //4.处理响应内容
          onResponseToClient(requestBytes, device, requestId, characteristic);
    }
    /**
     * 2.描述被写入时,在这里执行 bluetoothGattServer.sendResponse(device, requestId
     * , BluetoothGatt.GATT_SUCCESS...  收,触发 onCharacteristicWriteRequest
     */
    @Override
    public void onDescriptorWriteRequest(BluetoothDevice device, int requestId
            , BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded
            , int offset, byte[] value) {
        // now tell the connected device that this was all successfull
        bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset
                , value);
    }
    /**
     * 5.特征被读取。当回复响应成功后,客户端会读取然后触发本方法
     */
    @Override
    public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset
            , BluetoothGattDescriptor descriptor) {
        super.onDescriptorReadRequest(device, requestId, offset, descriptor);
        bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS
                , offset, null);
    }
    @Override
    public void onNotificationSent(BluetoothDevice device, int status) {
        super.onNotificationSent(device, status);
    }
    @Override
    public void onMtuChanged(BluetoothDevice device, int mtu) {
        super.onMtuChanged(device, mtu);
    }
    @Override
    public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
        super.onExecuteWrite(device, requestId, execute);
    }
};
处理来自客户端发来的数据和发送回复数据:
调用 bluetoothGattServer.notifyCharacteristicChanged 方法,通知数据改变。
private void onResponseToClient(byte[] reqeustBytes, BluetoothDevice device, int requestId
        , BluetoothGattCharacteristic characteristic) {
    String msg = OutputStringUtil.transferForPrint(reqeustBytes);
    Log.d("onResponseToClient", "4.收到:" + msg);
    
    String str = new String(reqeustBytes) + " hello>";
    characteristicRead.setValue(str.getBytes());
    bluetoothGattServer.notifyCharacteristicChanged(device, characteristicRead, false);
    Log.d("onResponseToClient", "4.响应:" + str);
}

交互流程:

(1) 当客户端开始写入数据时: 触发回调方法 onDescriptorWriteRequest
(2) 在 onDescriptorWriteRequest 方法中,执行下面的方法表示 写入成功 BluetoothGatt.GATT_SUCCESS

bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);

执行 sendResponse后,会触发回调方法 onCharacteristicWriteRequest
(3) 在 onCharacteristicWriteRequest方法中
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) {

这个里可以获得 来自客户端发来的数据 requestBytes
(4) 处理响应内容,我写了这个方法:
onResponseToClient(requestBytes, device, requestId, characteristic);
在这个方法中,通过 bluetoothGattServer.notifyCharacteristicChanged()方法 回复数据


通过日志,我们看看事件触发的顺序

1.onConnectionStateChange:device name = null, address = 74:32:DE:49:3C:28
1.onConnectionStateChange:status = 0, newState =2
2.onDescriptorWriteRequest:device name = null, address = 74:32:DE:49:3C:28
2.onDescriptorWriteRequest:requestId = 1, preparedWrite = false, responseNeeded = true, offset = 0, value = [01,00,],
3.onCharacteristicWriteRequest:device name = null, address = 74:32:DE:49:3C:28
3.onCharacteristicWriteRequest:requestId = 2, preparedWrite=false, responseNeeded=false, offset=0, value=[41,54,45,30,0D,]
4.onResponseToClient:device name = null, address = 74:32:DE:49:3C:28
4.onResponseToClient:requestId = 2
4.收到:ATE0
4.响应:ATE0 hello>
5.onNotificationSent:device name = null, address = 74:32:DE:49:3C:28
5.onNotificationSent:status = 0

参考:
http://www.ifeegoo.com/relationship-between-android-version-and-bluetooth-version.html
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁星点点-

请我喝杯咖啡呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值