Android BLE 操作

来源:互联网 发布:室内设计软件培训 编辑:程序博客网 时间:2024/06/05 05:34

:基本概念

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特有

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


)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


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

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


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

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

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

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


)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.增加蓝牙的权限

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

.获取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:MI1AI/BLE     (18109): address:88:0F:10:DA:67:63I/BLE     (18109): name:HUAWEI Band 2-86fI/BLE     (18109): address:78:62:56:7A:A8:6FI/BLE     (18109): name:MI1AI/BLE     (18109): address:88:0F:10:DA:67:63I/BLE     (18109): name:nullI/BLE     (18109): address:49:C3:C8:31:B1:00I/BLE     (18109): name:nullI/BLE     (18109): address:49:C3:C8:31:B1:00I/BLE     (18109): name:MI1AI/BLE     (18109): address:88:0F:10:DA:67:63I/BLE     (18109): name:HUAWEI Band 2-86fI/BLE     (18109): address:78:62:56:7A:A8:6F

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<BluetoothGattService> gattServices = gatt.getServices();                //获取所有Service的UUID                for(BluetoothGattService gattService : gattServices){                    uuid = gattService.getUuid().toString();                  //  Log.i("BLE","Services UUID:"+uuid);                    //获取Service的所有Char                    List<BluetoothGattCharacteristic> 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<BluetoothGattService> 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对应的UUIDBluetoothGattCharacteristic 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)

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:加上权限

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

除些之外还要在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);}@Overridepublic 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;    }}




三:把手机当作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测试软件

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


点击Vivo Xplay6 开始连接

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

对特征值进行写值0x18


打印:

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