Android BLE 操作

:基本概念

BLE:BluetoothLow Energy蓝牙低功耗技术,是蓝牙4.0引入的新技术,Android4.3中引进了对BLE的支持

蓝牙:Bluetooth是一种无线技术标准,短距离数据交换(使用2.4-2.485GHz)无线电波

蓝牙典型距离是10m以内,传输速度可达24Mbps(蓝牙3.0高速蓝牙)

蓝牙4.0/4.1 即低功耗蓝牙

蓝牙:5.0针对物联网方向的改进

BLE最大的特点就是低功耗,有些BLE设备一个纽扣电池可以使用一两年.

BLE功耗低那么能传输的速率就慢了,它被设计来传输少量的数据,适合物联网应用

GAP:定义了整个通信过程中的流程,如广播,扫描,连接流程

GATT:Generic Attributes,它定义了一套数据结构和BLE设备进行交互,蓝牙4.0特有

Android BLE 操作_第1张图片

这个结构包含了Service,Characteristic,可以用下图示意

Android BLE 操作_第2张图片

)Profile它是一个被定义的Service集合

例如心率Profile包含了HeartRate ServiceDeviceInformation Service

2)Service

把数据分成一个个单独的逻辑项,包含一个或者多个Characteristic

每个Service有一个UUID唯一标识

:HeartRate Service:

0000180d-0000-1000-8000-00805f9b34fb[0x180d]

DeviceInformation Service:

0000180a-0000-1000-8000-00805f9b34fb[0x180a]


16bitUUID:是通过官方证证的.Afee of $2,500 per UUID,需要购买

128bitUUID:自定义的UUID

例如:心率服务的UUID0x180d,它包含了三个Characteristic:

HeartRate Measurement:

00002a37-0000-1000-8000-00805f9b34fb

BodySensor Location

00002a38-0000-1000-8000-00805f9b34fb

HeartRate Control Point

0000fe86-0000-1000-8000-00805f9b34fb

且只有第一个是必须的,其它是可选的

蓝牙串口UUID

SerialPortServiceClass_UUID= ‘{00001101-0000-1000-8000-00805F9B34FB}’

LANAccessUsingPPPServiceClass_UUID= ‘{00001102-0000-1000-8000-00805F9B34FB}

UUID:唯一标识号,Service,Characteristic通过UUID来通信

BluetoothBase UUID:蓝牙基础UUID形如:

0000xxxx-0000-1000-8000-00805f9b34fb

其中xxxx是厂家的16bitUUID,其它的是固定不变的,16bitUUID对应128bitUUID8-4-4-4-12这个形式如

123ef678-1264-1008-126457894561

所以16位的UUID只有65536


3)Characteristic(特征):

是最小的逻辑数据单元,Service一样,每个Characteristic16bit或者128bitUUID唯一标识

Characteristic包含属性(Properties)、值(Value)、值的描述(Descriptpr,Characteristic的描述,例如范围,计量单位)

HeartRate MeasurmementUUID[0x2a37]


HeartRateMeasurement

org.bluetooth.characteristic.heart_rate_measurement

0x2A37

可以在官网查找

https://www.bluetooth.com/specifications/gatt/characteristics


外围设备:小和低功耗的设备,用来提供数据,如小米手环

中心设备:用来连接其它外围设备,如手机,平板

Android BLE 操作_第3张图片

中心设备可以连接多个外围设备,一般不超过7个,外围设备和一个中心设备连接

外围设备通过AdvertisingData Payload(广播数据)ScanResponse Data Payload(扫描回复)来向外广播,只有不停的向外广播中心设备才知道它的存在.

GATT通信双方是C/S关系,外设作为GATT服务端,它维持了ATT的查找表以及ServiceCharacteristic的定义,中心设备是GATT客户端Client,它向Server发起请求,并且接收服务端的响应

BLE协议栈从下到上可以分为三层,Controller控制器,Host(主机),Applications应用

Android BLE 操作_第4张图片

)BLEController

它是协议栈的底层实现,直接与硬件相关,直接集成到SOC,由芯片厂商实现,包括物理层和链路层

物理层:BLE是无线通信,物理层是一定频率范围下的频带资源2.4-2.4835GHz

为了支持多个设备,将整个频带分为40,每份的带宽为2MHzRF Channel

3个广播通道,37个数据通道,按照一定规律跳频通信(高斯频移键控 GFSK)

链路层:LinkLayer,PhysicalChannel上可以收发数据

HCI:定义HostController(通常是两颗IC)之间的通信协议,可基于UartUSB等物理介质

)BLEHost主机:这是协议栈的上层实现,是硬件的抽象,与具体的硬件和厂家无关

包括逻辑链路和适配层,安全管理模块等

GAP:定义了整个通信过程中的流程,如广播,扫描,连接流程

GATT:是一个Profile,包含ServiceCharacteristic

SM:Security Manager安全相关,包括配对pairing认证authentication加密encryption

3)应用层,使用Host层提供的API开发应用

android4.2是基于BlueZ实现的,4.2后换成了BlueDroid,4.3后支持BLE,5.0后才支持外设模式,6.0后需要申请定位权限

BLE应用可以分为两大类,基于非连接的和连接的

非连接的应用依赖于BLE广播,也叫作Beacon,发送广播的叫Broadcaster,监听广播的叫Observer

基于连接的应用是通过GATT连接,收发数据,发起连接的一方叫中心设备(手机),被连接的叫外设(手环)

BLE的连接速度可以达到3.75ms

.Android操作BLE

测试的Android系统为Android5.1

Android5.0开始,Android设备就可以像外设一样发送BLE广播了,Android设备之间可以通过BLE来交互数据

1.增加蓝牙的权限


.获取BluetoothAdapter

private BluetoothAdapter mBluetoothAdapter;

BluetoothAdapter类代表了本设备(手机,平板)的蓝牙适配器

通过它可以进行,蓝牙开关,扫描,获取蓝牙状态name,mac

BbluetoothManager =
        (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if(mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()){
    //1)请求打开蓝牙
   // startActivity(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE));
//2)隐式打开蓝牙
mBluetoothAdapter.enable();

}

BluetoothManager:管理蓝牙服务的一个类,主要用于得到一个蓝牙适配器BluetoothAdapter

3.扫描设备BluetoothLeScanner

private BluetoothLeScanner scanner;
scanner = mBluetoothAdapter.getBluetoothLeScanner();
//开机自动扫描
scanner.startScan(leScanCallback);

startScan()方法扫描周围的BLE设备

stopScan()方法停止扫描

public void startScan(ScanCallback callback)
public void stopScan(ScanCallback callback)


leScanCallback回调函数,通过onScanResult()把每次扫描到的设备添加到本地

BluetoothDevice类:代表了一个远端的蓝牙设备,使用它请求远端蓝牙设备连接或者获取远端蓝牙设备的名称、地址、种类和绑定状态


private ScanCallback leScanCallback = new ScanCallback() {
     @Override
     public void onScanResult(int callbackType, final ScanResult result) {
         super.onScanResult(callbackType, result);
         //result:包含BLE的信息,信号强度,和播数据
         //result.getDevice(),result.getDeviceName();
         //更新UI
         runOnUiThread(new Runnable() {
             @Override
             public void run() {
                 BluetoothDevice device = result.getDevice();
                 Log.i("BLE","name:"+device.getName()+"\n"+
                                      "address:"+device.getAddress());
                 mLeDeviceScanAdapter.addDevice(device);
                 mLeDeviceScanAdapter.notifyDataSetChanged();
             }
         });
     }
     @Override
     public void onScanFailed(int errorCode) {
         super.onScanFailed(errorCode);
         Log.i(TAG,"描扫失败"+errorCode);
     }
 };
I/BLE     (18109): name:MI1A
I/BLE     (18109): address:88:0F:10:DA:67:63
I/BLE     (18109): name:HUAWEI Band 2-86f
I/BLE     (18109): address:78:62:56:7A:A8:6F
I/BLE     (18109): name:MI1A
I/BLE     (18109): address:88:0F:10:DA:67:63
I/BLE     (18109): name:null
I/BLE     (18109): address:49:C3:C8:31:B1:00
I/BLE     (18109): name:null
I/BLE     (18109): address:49:C3:C8:31:B1:00
I/BLE     (18109): name:MI1A
I/BLE     (18109): address:88:0F:10:DA:67:63
I/BLE     (18109): name:HUAWEI Band 2-86f
I/BLE     (18109): address:78:62:56:7A:A8:6F
Android BLE 操作_第5张图片

4.连接设备

BLE连接的建立是通过GAP来协商的,中心设备发起连接,外设接收连接请求

GATT的核心内容是Service,Characteristic以及Descriptor,最重要的是获取Service中的Characteristic,Characteristic可以被读,,有变化的时候有通知,实现双向通信

通过 BluetoothDeviceconnectGatt()方法得到BluetoothGatt,表示一个连接,用完以后,记得close()来释放资源

public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)

参数1:上下文

参数2:是否自动连接

参数3:回调

public BluetoothDevice getRemoteDevice(String address)

以给定MAC地址address去创建一个BluetoothDevice类实例(代表远程蓝牙实例)

private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothGatt    mBluetoothGatt;
private BluetoothDevice  mBluetoothDevice;

mBluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = mBluetoothManager.getAdapter();
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(address);
mBluetoothGatt = mBluetoothDevice.connectGatt(this,
        false,bleGattCallback);

连接过程是,首先使用connectGatt发起连接,收到onConnectionStateChange()通知连接是否成功,若成功,则进行下一步的discoverService(),这一步就是发现设备所有的GATTService,若发现成功,通过onServiceDiscovered()回调,这时才算真正的连接成功。然后可以通过BluetoothGattgetService()来获得BluetoothGattService,进而获得BluetoothGattCharacteristic等,然后对Characteristic进行读写。

这些过程主要是在回调函数bleGattCallback里实现


BLE 连接回调函数
BluetoothGattCallback bleGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            Log.i("BLE","onConnectionStateChange");
            if(newState== BluetoothProfile.STATE_CONNECTED){
                //发现设备的所有 GATT server
                gatt.discoverServices();
               runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       Log.i("BLE","已连接");
                       tvState.setText("已连接");
                   }
               });

            }else if(newState == BluetoothProfile.STATE_DISCONNECTED){
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Log.i("BLE","已断开");
                        tvState.setText("已断开");
                    }
                });
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            Log.i("BLE","onServiceDiscovered");
            String uuid = null;
            if(status==BluetoothGatt.GATT_SUCCESS){
                List gattServices = gatt.getServices();
                //获取所有Service的UUID
                for(BluetoothGattService gattService : gattServices){
                    uuid = gattService.getUuid().toString();
                  //  Log.i("BLE","Services UUID:"+uuid);
                    //获取Service的所有Char
                    List gattCharacteristics =
                            gattService.getCharacteristics();
                    for(BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics){
                        uuid = gattCharacteristic.getUuid().toString();
                     //   Log.i("BLE","Characteristic UUID:"+uuid);

                    }
                }



            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            Log.i("BLE","onCharacteristicRead");
            String uuid = null;
            if(status == BluetoothGatt.GATT_SUCCESS){
                uuid = characteristic.getUuid().toString();
                if(uuid.equals("0000ff0c-0000-1000-8000-00805f9b34fb")){
                    final byte[] data = characteristic.getValue();
                    //data[0]为手环的电量
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            tvBattery.setText(data[0]+"%");
                        }
                    });
                }

            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            String uuid = null;
            Log.i("BLE","onCharacteristicWrite");

            if(status == BluetoothGatt.GATT_SUCCESS){
                uuid = characteristic.getUuid().toString();
                if(uuid.equals("0000ff05-0000-1000-8000-00805f9b34fb")){


                }
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);

            Log.i("BLE","onCharacteristicChanged");
        }
    };

调用流程,APP发起请求,ble设备回调

.BLE手环通信,读写BLE


public List getServices()
public boolean readCharacteristic(BluetoothGattCharacteristic characteristic)
//读数据
当调用gatt.readCharacteristic(gattCharacteristic)时会触发读回调函数
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
从 onCharacteristicRead获取读取的数据
final byte[] data = characteristic.getValue();

//电量读取是在UUID0xfee0里的特征UUID0xff0c
//1)获取Service对象
BluetoothGattService service =
        mBluetoothGatt.getService(UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"));
//2)获取characteristic对应的UUID
BluetoothGattCharacteristic characteristic =
        service.getCharacteristic(UUID.fromString("0000ff0c-0000-1000-8000-00805f9b34fb"));
//3)读数据
mBluetoothGatt.readCharacteristic(characteristic);

//写数据
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic)
当调用gatt.writeCharacteristic(gattCharacteristic);
写数据时会触发回调函数
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)

Android BLE 操作_第6张图片

btReadBattery.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        //电量读取是在UUID0xfee0里的特征UUID0xff0c
        //1)获取Service对象
        BluetoothGattService service =
                mBluetoothGatt.getService(UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"));
        //2)获取characteristic对应的UUID
        BluetoothGattCharacteristic characteristic =
                service.getCharacteristic(UUID.fromString("0000ff0c-0000-1000-8000-00805f9b34fb"));
        //3)读数据
        mBluetoothGatt.readCharacteristic(characteristic);
    }
});
btFind.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        //找回手环,是发指令让手环发光+振动
        //服务UUID:0x1802 特征UUID:0x2a06
        //1)获取Service对象
        BluetoothGattService service =
                mBluetoothGatt.getService(UUID.fromString("00001802-0000-1000-8000-00805f9b34fb"));
        //2)获取characteristic对应的UUID
        BluetoothGattCharacteristic characteristic =
                service.getCharacteristic(UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb"));
        //3)发送指令0x02
        byte[] cmd = {0x02};
        characteristic.setValue(cmd);
        boolean ret = mBluetoothGatt.writeCharacteristic(characteristic);
    }
});

以上的测试是基于Android5.1的系统


6.Android7.1上测试

在扫描设备的时候,报安全错误,导至扫描不到设备

java.lang.SecurityException:Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to getscan results

fix:加上权限



除些之外还要在app里添加运行时权限检查

AndroidM开始,Google就正式推出了官方的权限管理机制AndroidRuntime Permission

在扫描代码之前添加

//运行时权限检查
if(checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
        != PackageManager.PERMISSION_GRANTED){
    mNumPermissionsToRequest++;
    mShouldRequestLocationPermission = true;
}else{
    mFlagHasLocationPermission = true;
}
String[] permissionToRequest = new String[mNumPermissionsToRequest];
int permissionRequestIndex = 0;
if(mShouldRequestLocationPermission){
    permissionToRequest[permissionRequestIndex] =
            Manifest.permission.ACCESS_COARSE_LOCATION;
    mIndexPermissionRequestLocation = permissionRequestIndex;
    permissionRequestIndex++;
}
if(permissionToRequest.length > 0){
    requestPermissions(permissionToRequest, 0);
}
@Override
public void onRequestPermissionsResult(int requestCode,
                                       String permissions[],
                                       int[] grantResults){
    switch (requestCode){
        case 0:
            if(grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                Log.i("BLE","Grant permission successfully");
            }else{
                Log.i("BLE","Grant permission unsuccessfully");
            }
            break;
        default:
            break;
    }
}
Android BLE 操作_第7张图片
Android BLE 操作_第8张图片


三:把手机当作BLE 从设备

从Android5.0后开始就支持把一个中心设备的BLE当成外围设备的BLE

这里把手机当成一个外设BLE设备,用另外一台手机和它通信

1.查查设备是否支持ble外设功能

如果支持则构建广播,只有当广播发出去后,才能被中心设备所发现

//设备是否支持ble围设备通信
 mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
if(mBluetoothLeAdvertiser == null){
    Log.i("BLE","不支持 Peripheral 模式");
}else{
    Log.i("BLE","支持 Peripheral 模式");
    //1)广    AdvertiseSettings.Builder settingBuilder = new AdvertiseSettings.Builder();
    //广广    //广播模式:ADVERTISE_MODE_LOW_POWER/BALANCED/LOW_LATENCY
    //低功耗/平衡/低延    settingBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED);
    settingBuilder.setConnectable(true);
    settingBuilder.setTimeout(0);//置最时时间0表示一直广    //广播信号强    settingBuilder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
    AdvertiseSettings settings = settingBuilder.build();
    //2)广参数
    AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
    mBluetoothAdapter.setName("ViVO Xplay6");
    dataBuilder.setIncludeDeviceName(true);
    dataBuilder.setIncludeTxPowerLevel(true);
    dataBuilder.addServiceUuid(ParcelUuid.
            fromString("0000180d-0000-1000-8000-00805f9b34fb"));
    AdvertiseData data = dataBuilder.build();
    //3)广    mBluetoothLeAdvertiser.startAdvertising(settings,
            data,
            advertiseCallback);
}

2.开始广播后的回调

//广播回private AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
    @Override
    public void onStartSuccess(AdvertiseSettings settingsInEffect) {
        super.onStartSuccess(settingsInEffect);
        if(settingsInEffect != null){
            Log.i("BLE","onStartSuccess txpower="+
            settingsInEffect.getTxPowerLevel());
            BluetoothManager bluetoothManager =
                    (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);

            mBluetoothGattServer = bluetoothManager.openGattServer(getApplicationContext(),
                    bluetoothGattServerCallback);
            BluetoothGattService service = new BluetoothGattService(
                    UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb"),
                            BluetoothGattService.SERVICE_TYPE_PRIMARY);
            //特征值读写设            BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(
                    UUID.fromString("0000ff01-0000-1000-8000-00805f9b34fb"),
                    BluetoothGattCharacteristic.PROPERTY_WRITE |
                            BluetoothGattCharacteristic.PROPERTY_READ,
                    BluetoothGattCharacteristic.PERMISSION_WRITE
            );

            service.addCharacteristic(characteristicWrite);
            mBluetoothGattServer.addService(service);



        }else{
            Log.i("BLE","onStartSuccess, settingInEffect is null");
        }
    }
    //    private BluetoothGattServerCallback bluetoothGattServerCallback = new BluetoothGattServerCallback() {
        @Override
        public void onServiceAdded(int status, BluetoothGattService service) {
            super.onServiceAdded(status, service);
            Log.i("BLE","onServiceAdded:"+service.getUuid().toString());
        }

        @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            super.onConnectionStateChange(device, status, newState);
            Log.i("BLE","onConnectionStateChange"+"" +
                    "status:"+status+" ->newState:"+newState);
        }

        @Override
        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
            Log.i("BLE","onCharacteristicWriteRequest");

            mBluetoothGattServer.sendResponse(device,
                    requestId,
                    BluetoothGatt.GATT_SUCCESS,
                    offset,
                    value);
            String info = "Request:"+requestId+"|Offset:"+offset+
                    "|characteristic:"+characteristic.getUuid()+
                    "|Values:"+bytesToHexString(value,value.length);
            Log.i("BLE","据信息:"+info);
        }
    };
    @Override
    public void onStartFailure(int errorCode) {
        super.onStartFailure(errorCode);
        Log.i("BLE","onStartFailure errorCode="+errorCode);
    }
};

public String bytesToHexString(byte[] src, int len){
    StringBuilder stringBuilder = new StringBuilder("");
    if (src == null || src.length <= 0 || src.length < len) {
        return null;
    }
    for (int i = 0; i < len; i++) {
        int v = src[i] & 0xFF;
        String hv = Integer.toHexString(v);
        if (hv.length() < 2) {
            stringBuilder.append(0);
        }
        stringBuilder.append(hv);
    }

    return stringBuilder.toString();
}

3.测试

手机运行软件后,打印

I/BLE     (32289): 支持 Peripheral 模式

I/BLE     (32289): onStartSuccess txpower=3 //发送广播成功
I/BLE     (32289): onServiceAdded:0000180d-0000-1000-8000-00805f9b34fb //添加了server

在另一台手机运行nRF Connect测试软件

可以扫描到打开广播的手机

Android BLE 操作_第9张图片

点击Vivo Xplay6 开始连接

连接成功打印I/BLE     ( 5695): onConnectionStateChangestatus:0 ->newState:2

对特征值进行写值0x18

Android BLE 操作_第10张图片

打印:

I/BLE     ( 5695): onCharacteristicWriteRequest
I/BLE     ( 5695): 数据信息:Request:1|Offset:0|characteristic:0000ff010-1000-8000-00805f9b34fb|Values:18
说明值已写入,读类似,实现了两台手机通信






参考:

https://www.bluetooth.com/

http://www.jianshu.com/p/93f795c210b6

http://www.blogjava.net/baicker/archive/2015/09/05/427125.html

https://www.bluetooth.com/specifications/gatt/generic-attributes-overview

https://learn.adafruit.com/introduction-to-bluetooth-low-energy?view=all

http://www.wowotech.net/bluetooth/bt_overview.html



你可能感兴趣的:(Android,应用)