Android BLE蓝牙4.0开发详解

Android BLE蓝牙4.0开发详解

1、简介
    一、蓝牙版本介绍:
    蓝牙发展至今经历了8个版本的更新。1.一、1.二、2.0、2.一、3.0、4.0、4.一、4.2。那么在1.x~3.0之间的咱们称之为传统蓝牙,4.x开始的蓝牙咱们称之为低功耗蓝牙(即蓝牙ble),固然4.x版本的蓝牙也是向下兼容的。android手机必须系统版本4.3及以上才支持BLE API。

    二、蓝牙低功耗芯片有两种模式:单模和双模。
    单模:只能执行低功耗协议栈,也就是只支持ble。
        双模:支持传统蓝牙以及ble的使用。
   
    三、传统蓝牙低功耗蓝牙区别:
    1)低功耗蓝牙较传统蓝牙,传输速度更快,覆盖范围更广,安全性更高,延迟更短,耗电极低等等优势。这也是为何近年来智能穿戴的东西愈来愈多,愈来愈火。
    2)还有传统蓝牙与低功耗蓝牙通讯方式也有所不一样,传统的通常经过socket方式,而低功耗蓝牙是经过Gatt协议来实现。如果以前没作过传统蓝牙开发,也是能够直接上手低功耗蓝牙开发的。由于它们在通讯协议上都有所改变,关联不大。
    3)低功耗蓝牙也叫BLE,下面都称之为BLE

2、关键术语和概念:
一、Gatt:(Generic Attribute Profile)—通用属性配置文件,用于在ble链路上发送和接收被称为“属性”的数据块。目前全部的ble应用都是基于GATT的。一个设备能够实现多个配置文件。

二、ble交互的桥梁是Service、Characteristic、Desciptor。

三、Characteristic:能够理解为一个数据类型,它包括一个value和0至多个对此characteristic的描述(Descriptor)。

四、Descriptor:对Characterisctic的描述,如范围、单位等。

五、Service:Characteristic的集合。它能够包含多个Characteristic。

六、BLE分为三部分:Service,Characteristic,Descriptor。这三部分都用UUID做为惟一标识符。UUID为这种格式:0000ffe1-0000-1000-8000-00805f9b34fb。好比有3个Service,那么就有三个不一样的UUID与Service对应。这些UUID都写在硬件里,咱们经过BLE提供的API能够读取到。
    一个BLE终端能够包含多个Service, 一个Service能够包含多个Characteristic,一个Characteristic包含一个value和多个Descriptor,一个Descriptor包含一个Value。Characteristic是比较重要的,是手机与BLE终端交换数据的关键,读取设置数据等操做都是操做Characteristic的相关属性。
    好比我有个BLE的硬件,咱们能够用android 版本的light blue去链接上这个硬件,进入应用,就能够扫描到你的BLE设备,点击就会链接上,而后咱们能够看到UUID列表,这里每一行的UUID都表明一个Service,再点击任意一行进去,又能够看到一个UUID列表,这里每一行的UUID都表明一个Characteristic,再点击任意一行进去,便可以操做这个Characteristic,好比写入数据或者读出数据等。

七、UUID(Universally Unique Identifier),含义是通用惟一识别码,它是在必定范围内惟一的机器生成的标识符。标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)。

八、ble中有四个角色:

广播者(Braodcaster):广播发送者,是不可链接的设备。

观察者(Observer):扫描广播,不可以启动链接。

广播者和观察者不能创建链接。应用:温度传感器和温度显示器。

---------------------------------------------------------------------

外围(periphery):广播发送者,可链接的设备,在单一链路层做为从机。

中央(central):扫描广播,启动链接,在单一或多链路层做为主机。

中央和外围能够进行配对、链接、数据通讯。应用:手机和手表。

一个中央能够同时链接多个周边,可是一个周边只能链接一个中央(可是我测试,周边能够链接多个中央设备,而且能正常通讯)。


3、BLE蓝牙4.0 android开发(中央设备)java


注意:Android 4.3(API 18)引入ble相关接口。
相关类
目录:frameworks/base/core/java/android/bluetooth/
BluetoothGatt:中央使用和处理数据;
BluetoothGattCallback:中央的回调。

BluetoothGattServer:周边提供数据;
BluetoothGattServerCallback:周边的回调

BluetoothGattService:Gatt服务
BluetoothGattCharacteristic:Gatt特性
BluetoothGattDescriptor:Gatt描述


一、首先在程序里咱们要开启蓝牙权限
<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"></uses-feature>


二、判断设备是否支持蓝牙ble:
getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)


三、获取蓝牙适配器BluetoothAdapter
    final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);  
    BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();  
或者:
    //若是mBluetoothAdapter == null,说明设备不支持蓝牙  
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();       


四、启动、禁止蓝牙:弹出是否启用蓝牙的对话框
    //也能够直接调用mBluetoothAdapter.enable()mBluetoothAdapter.disable()来启用禁用蓝牙。不过这种方式不会弹出询问对话框  
    if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {  
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);  
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);  
    }  
    @Override  
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
        switch (requestCode) {  
        case REQUEST_ENABLE:  
            if (resultCode == Activity.REQUEST_ENABLE_BT) {  
                Toast.makeText(this, "蓝牙已启用", Toast.LENGTH_SHORT).show();  
            } else {  
                Toast.makeText(this, "蓝牙未启用", Toast.LENGTH_SHORT).show();  
            }  
            break;  
        }  
    }  


五、搜索蓝牙设备,回调。
mBluetoothAdapter.startLeScan(mLeScanCallback); //开始搜索附近全部的外围设备

//搜索某些uuid的外围设备。  
mBluetoothAdapter.startLeScan(uuid[] ,mLeScanCallback);  
 
mBluetoothAdapter.stopLeScan(mLeScanCallback);//中止搜索  

    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {  
        @Override  
        public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {    
            //device 搜索到的ble设备;rssi 信号强度;scanRecord 远程设备扫描记录的内容(蓝牙名称)
                    //device.getName();获取蓝牙设备名字  
                    //device.getAddress();获取蓝牙设备mac地址  
                
            });  
        }  
    };  



六、选择一个设备进行链接。链接后会返回一个BluetoothGatt 类型的对象,这里定义为mBluetoothGatt。该对象比较重要,后面发现服务读写设备等操做都是经过该对象。

//第二个参数:若是为false,则直接当即链接。若是为true,则等待远程设备可用时(在范围内,。。)链接。并非断开后从新链接。
 mBluetoothGatt = device.connectGatt(mContext, false,mGattCallback);


    public boolean connect(final String address) {  
        if (mBluetoothAdapter == null || address == null) {  
            Log.w(TAG,"BluetoothAdapter not initialized or unspecified address.");  
            return false;  
        }  
        // Previously connected device. Try to reconnect. (先前链接的设备。 尝试从新链接)  
        if (mBluetoothDeviceAddress != null&& address.equals(mBluetoothDeviceAddress)&& mBluetoothGatt != null) {  
            Log.d(TAG,"Trying to use an existing mBluetoothGatt for connection.");  
            if (mBluetoothGatt.connect()) {  
                mConnectionState = STATE_CONNECTING;  
                return true;  
            } else {  
                return false;  
            }  
        }  
        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);  
        if (device == null) {  
            Log.w(TAG, "Device not found.  Unable to connect.");  
            return false;  
        }  
        // We want to directly connect to the device, so we are setting the  
        // autoConnect  
        // parameter to false.  
        mBluetoothGatt = device.connectGatt(this, false, mGattCallback); //该函数才是真正的去进行链接  
        Log.d(TAG, "Trying to create a new connection.");  
        mBluetoothDeviceAddress = address;  
        mConnectionState = STATE_CONNECTING;  
        return true;  
    }  


链接后会回调BluetoothGattCallback接口,包括读取设备,往设备里写数据及设备发出通知等都会回调该接口。
    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {  
            @Override  //当链接上设备或者失去链接时会回调该函数  
            public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) {  
                if (newState == BluetoothProfile.STATE_CONNECTED) { //链接成功  
                            mBluetoothGatt.discoverServices(); //链接成功后就去找出该设备中的服务 private BluetoothGatt mBluetoothGatt;  
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {  //链接失败  
                }  
            }  
            @Override  //当设备是否找到服务时,会回调该函数  
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {  
                if (status == BluetoothGatt.GATT_SUCCESS) {   //找到服务了  
                    //在这里能够对服务进行解析,寻找到你须要的服务
            //此处返回获取到的服务列表  
            mBluetoothGatt.getServices();
                } else {  
                    Log.w(TAG, "onServicesDiscovered received: " + status);  
                }  
            }  
            @Override  //当读取设备时会回调该函数  
            public void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {  
                System.out.println("onCharacteristicRead");  
                if (status == BluetoothGatt.GATT_SUCCESS) {  
                  //读取到的数据存在characteristic当中,能够经过characteristic.getValue();函数取出。而后再进行解析操做。  
                              //int charaProp = characteristic.getProperties();if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0)表示可发出通知。  判断该Characteristic属性  
                }  
            }  
      
            @Override //当向设备Descriptor中写数据时,会回调该函数  
            public void onDescriptorWrite(BluetoothGatt gatt,BluetoothGattDescriptor descriptor, int status) {  
                System.out.println("onDescriptorWriteonDescriptorWrite = " + status + ", descriptor =" + descriptor.getUuid().toString());  
            }  
      
            @Override //设备发出通知时会调用到该接口  
            public void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic) {  
                if (characteristic.getValue() != null) {  
                      System.out.println(characteristic.getStringValue(0));  
                }  
                System.out.println("--------onCharacteristicChanged-----");  
            }  
      
            @Override  
            public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {  
                System.out.println("rssi = " + rssi);  
            }  
                    @Override //当向Characteristic写数据时会回调该函数  
                    public void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {  
                               System.out.println("--------write success----- status:" + status);  
                    };  
    }  


七、发现服务,解析出有哪些服务,服务里有哪些Characteristic,哪些Characteristic可读可写可发通知等等
mBluetoothGatt.discoverServices();去发现服务。发现服务后会回调BluetoothGattCallback接口里面的 onServicesDiscovered函数。
    //在里面咱们能够获取服务列表
   public List<BluetoothGattService> getSupportedGattServices() {  
        if (mBluetoothGatt == null)  
            return null;  
        return mBluetoothGatt.getServices();   //此处返回获取到的服务列表  
    }  


    //解析出有哪些服务,服务里有哪些Characteristic,哪些Characteristic可读可写可发通知等等
    private void displayGattServices(List<BluetoothGattService> gattServices) {  
        if (gattServices == null)  
            return;  
        for (BluetoothGattService gattService : gattServices) { // 遍历出gattServices里面的全部服务  
            List<BluetoothGattCharacteristic> gattCharacteristics = gattServices.getCharacteristics();  
            for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { // 遍历每条服务里的全部Characteristic  
                if (gattCharacteristic.getUuid().toString().equalsIgnoreCase(须要通讯的UUID)) {   
                     // 有哪些UUID,每一个UUID有什么属性及做用,通常硬件工程师都会给相应的文档。咱们程序也能够读取其属性判断其属性。  
                    // 此处能够可根据UUID的类型对设备进行读操做,写操做,设置notification等操做  
                    // BluetoothGattCharacteristic gattNoticCharacteristic 假设是可设置通知的Characteristic  
                    // BluetoothGattCharacteristic gattWriteCharacteristic 假设是可读的Characteristic  
                    // BluetoothGattCharacteristic gattReadCharacteristic  假设是可写的Characteristic  
                }  
            }  
        }  
    }  


经过以下代码能够获取某一个服务(BluetoothGattService)对应BluetoothGattCharacteristic的特性:是否可读、可写、可通知等。
            StringBuilder property = new StringBuilder();
            int charaProp = gattCharacteristics.getProperties();
            if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
                property.append("Read");
                property.append(" , ");
            }
            if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
                property.append("Write");
                property.append(" , ");
            }
            if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) {
                property.append("Write No Response");
                property.append(" , ");
            }
            if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                property.append("Notify");
                property.append(" , ");
            }
            if ((charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
                property.append("Indicate");
                property.append(" , ");
            }

八、经过上步解析出来的哪些Characteristic可读可写可发通知等等,选取本身须要的BluetoothGattService及对应的BluetoothGattCharacteristic,可经过UUID指定本身须要通讯的UUID。进而进行读写通知等操做
    
    BluetoothGattService、BluetoothGattCharacteristic和BluetoothGattDescriptor三个类中都提供了一个方法getUuid(),经过该方法能够获取其对应的uuid,从而能够判断是不是本身须要的service、characteristic或者descriptor。
经过获取的特征值,能够进行下操做:
写入特性值
读取特性值
订阅特性值。

1)写入特征值:
characteristic.setValue(data.getBytes());
mBluetoothGatt.writeCharacteristic(characteristic);

要想成功写入特征值:
    首先此characteristic属性知足BluetoothGattCharacteristic.PROPERTY_WRITY或BluetoothGattCharacteristic.PROPERTY_WRITY_NO_RESPONSE,若是其property都不包含这两个,写特征值writeCharacteristic()函数直接返回false,什么都不作处理(具体能够看BluetoothGatt源码)。
    其次此characteristic权限应知足BluetoothGattCharacteristic.PERMISSION_WRITE,不然onCharacteristicWrite()回调收到GATT_WRITE_NOT_PERMITTED回应。

写特征值前能够设置写的类型setWriteType(),写类型有三种,以下:
    WRITE_TYPE_DEFAULT  默认类型,须要外围设备的确认,也就是须要外围设备的回应,这样才能继续发送写。
    WRITE_TYPE_NO_RESPONSE 设置该类型不须要外围设备的回应,能够继续写数据。加快传输速率。
    WRITE_TYPE_SIGNED  写特征携带认证签名,具体做用不太清楚。

外围设备收到中央写特征值的请求,会回调 onCharacteristicWriteRequest
若是这次请求须要回应,则外围设备回应 mGattServer.sendResponse
中央设备收到响应,回调onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)


代码:
    //可写的UUID。下面函数参数为上面的gattWriteCharacteristic。writeCharacteristic调用成功会回调步骤6中的onCharacteristicWrite函数
    public void wirteCharacteristic(BluetoothGattCharacteristic characteristic) {  
      
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {  
            Log.w(TAG, "BluetoothAdapter not initialized");  
            return;  
        }  
        characteristic.setValue("xxxx");
        mBluetoothGatt.writeCharacteristic(characteristic);  
      
    }  

2)读取特性值:
mBluetoothGatt.readCharacteristic(characteristic);


读特征值与写相似,也须要响应的权限和属性。
    该characteristic属性需包含PROPERTY_READ,不然直接返回false(具体能够看BluetoothGatt源码)。
    该characteristic权限应知足BluetoothGattCharacteristic.PERMISSION_READ,不然onCharacteristicRead()回调收到GATT_READ_NOT_PERMITTED回应。


外围设备接收到中央设备的读特征值请求,则会回调 onCharacteristicReadRequest()函数,
外围设备应该回应此请求 sendResponse。
中央设备收到响应回调onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)


代码:
    //可读的UUID。下面函数参数为上面的gattReadCharacteristic。readCharacteristic调用成功会回调步骤6中的onCharacteristicRead函数
    public void readCharacteristic(BluetoothGattCharacteristic characteristic) {  
        if (mBluetoothAdapter == null || mBluetoothGatt == null) {  
            Log.w(TAG, "BluetoothAdapter not initialized");  
            return;  
        }  
        mBluetoothGatt.readCharacteristic(characteristic);  
    }  

3)订阅特性值
//第二个参数:true则订阅该特征,false则取消订阅。
mBluetoothGatt.setCharacteristicNotification(characteristic, true);

当指定Characteristic值发生变化时,是否接收通知。
当设为true,若是Characteristic发生变化时,会回调方法:onCharacteristicChanged(BluetoothGatt gatt,   BluetoothGattCharacteristic characteristic)
经过参数characteristic,可得到getValue得到其中的内容。

//可接收通知的UUID,设置其能够接收通知(notification)。下面函数参数为上面的gattNoticCharacteristic
    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {  
            if (mBluetoothAdapter == null || mBluetoothGatt == null) {  
                Log.w(TAG, "BluetoothAdapter not initialized");  
                return;  
            }  
            mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);  
            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID  
                    .fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));  
            if (descriptor != null) {  
                System.out.println("write descriptor");  
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);  
                mBluetoothGatt.writeDescriptor(descriptor);  
            }  
      
    }  

九、中央设备的其余一些方法

readDescriptor(descriptor)  读取描述

writeDescriptor(descriptor)  写描述

readRemoteRssi()            读取链接设备的rssi。

disconnect();       断开bel链接。

close();                 关闭中央设备。(不用时及时关闭,不然有的手机重连连不上。)


4、BLE蓝牙4.0 android开发(外围设备)
外围设备开发可参考:http://blog.csdn.net/vnanyesheshou/article/details/51943870

5、开源框架:android

1)FastBle:可安装APK,查看源码及git文档学习使用 github:https://github.com/Jasonchenlijian/FastBle 2)luetoothKit:可查看github源码及README.md文档查看使用 github:https://github.com/dingjikerbo/BluetoothKit