>

搜索成功并发现有可用的设备后sbf282.com:,所以

- 编辑:澳门博发娱乐官网 -

搜索成功并发现有可用的设备后sbf282.com:,所以

很多同学在做蓝牙开发的时候,很有可能你的设备是支持ancs协议的,那么ancs协议在首次连接app蓝牙的时候就会配对,之后只要你手机开着蓝牙,它都会自动连接。这时候,你再进入app,会发现明明在设置里面显示蓝牙已经连接,但是你在app怎么搜索都搜索不到改蓝牙外设。这个时候,就需要用到 retrieveConnectedPeripheralsWithServices 这个方法了。大概思路就是 当用户进去app,只要发现手机的蓝牙已经开启,那么就可以执行以下代码:

简介

最近做了一个智能门锁利用蓝牙与app交互的项目。整理一下相关蓝牙知识。下面要讲的是利用原生<CoreBluetooth.framework>框架封装demo,且支持蓝牙4.0。蓝牙官方文档

Android Bluetooth Low Energy

Android 低功耗蓝牙简介
2016-4-18

Android4.3(API 18)介绍了平台支持的低功耗蓝牙,app可用于发现设备,检索服务和读写特性(characteristics)。相比于传统蓝牙,低功耗蓝牙(BLE)消耗更少。它能够连接到外围的BLE设备,比如距离感应器、心率感应器、健康设备等等。

与外围设备交互的最佳实践

原文:Best Practices for Interacting with a Remote Peripheral Device

CoreBluetooth框架使许多中心端的事务对你的app可见的。也就是说,你的app控制它,并且负责实现作为中心角色的大多数方面,例如设备搜索和连接,外围设备数据的探索和交互。在你开发iOS设备app时,这章指南提供了处理这层控制的最佳实践。

最近研究了iOS下连接蓝牙打印机,实现打印购物小票的功能,对iOS中BLE 4.0的使用有了一定的了解,这里记录一下对BLE 4.0的理解。由于很多文章同时讲CBCentralManager和CBPeripheralManager,所以很容易傻傻分不清楚。很少把iPhone作为蓝牙外设在广播发送数据的情形,今天我就从iOS app开发的角度讲一些BLE 4.0的使用。

 //已经被系统或者其他APP连接上的设备数组 NSArray *arr = [self.cmgr retrieveConnectedPeripheralsWithServices:@[[CBUUID UUIDWithString:serviceUUID]]];//serviceUUID就是你首次连接配对的蓝牙 NSLog(@"%@",arr); if(arr.count>0) { for (CBPeripheral* peripheral in arr) { if (peripheral != nil) { self.peripherale = peripheral;//这一个必须要,需要一个全局变量接受,否则无法连接 [self.cmgr connectPeripheral:self.peripherale options:nil]; } } }

背景

蓝牙分为两种形式: 1)中心者模式 2)管理者模式,一般绝大部分我们都是使用第一种模式,中心者模式是我们手机作为主机,连接蓝牙外设,而管理者模式是我们手机自己作为外设,自己创建服务和特征,然后有其他的设备连接我们的手机。

接下来我们就是围绕第一种模式:中心者模式来讲解。

关键词和概念

  • Generic Attribute Profile (GATT)
    GATT模式是通过BLE连接发送接收“attributes”短数据的通用模式。目前所有的低功耗应用都基于GATT。
    蓝牙SIG定义了许多的低功耗设备模式。一个配置(profile)是设备在特定应用下的工作方式。请注意,一个设备可以实现不止一个配置。比如,一个设备可以同时包含心率感应器和一个电量检测器。

  • Attribute Protocol (ATT)
    GATT建立在属性协议(ATT)之上。这也可以看为GATT/ATT。ATT有针对BLE设备的优化。ATT使用尽可能少的字节。每个特性(attributes)都由一个UUID来标示。UUID(Universally Unique Identifier)是一个标准128位的字符串ID,用于识别单一的信息。ATT以服务(services)和属性(characteristics)来传送特性(attributes)。

  • Characteristic
    一个characteristic包含一个单独的值和0~n个描述值的descriptor。一个characteristic可以被当做是类的类型。

  • Descriptor
    用于描述characteristic的值。比如一个描述器可能指定一个可读的描述语句。

  • Service
    service是characteristics的集合。比如你可以有一个service叫做“Heart Rate Monitor”,里面包含多个characteristic,比如“heart rate measurement”。

注意广播使用和电池消耗

当你开发app和低功耗设备交互时,请记住低功耗设备的通讯共享你设备的无线广播信号。由于其他形式的无线通讯也需要占用你设备的广播,如wifi,老版本蓝牙,甚至其他占用低功耗蓝牙的app,所以开发你的app时请减少对广播的占用。

当你开发iOS设备app时,减少广播的使用非常重要,因为广播的使用影响iOS设备的电池寿命。下面的指南将会帮助你做一个良好的设备广播使用者,这样你的app将会运行的更好,你设备的电池寿命会更长久。

CBPeripheral 蓝牙外设,比如蓝牙手环、蓝牙心跳监视器、蓝牙打印机。CBCentralManager 蓝牙外设管理中心,与手机的蓝牙硬件模板关联,可以获取到手机中蓝牙模块的一些状态等,但是管理的就是蓝牙外设。CBService 蓝牙外设的服务,每一个蓝牙外设都有0个或者多个服务。而每一个蓝牙服务又可能包含0个或者多个蓝牙服务,也可能包含0个或者多个蓝牙特性。CBCharacteristic 每一个蓝牙特性中都包含有一些数据或者信息。

要注意的是,手机作为中心者模式,是可以连接多个从机的,你要必须从连接到的数组里面找到你要连接的蓝牙外设

步骤

蓝牙连接可以大致分为以下几个步骤:1.建立一个Central Manager实例进行蓝牙管理2.搜索外围设备3.连接外围设备4.获得外围设备的服务5.获得服务的特征6.从外围设备读数据、给外围设备发送数据

简言之:就是我们的app创建一个蓝牙中心管理者对象,调用SDK的方法去搜索周围可发现的设备,搜索成功并发现有可用的设备后,进行连接,连接成功后再获取设备的服务与特征,最后进行数据的交互。疑问:什么是服务?什么是特征?下面用一张图进行讲解~

sbf282.com 1各层级关系简言之:就是一个外设有几个服务,每一个服务又有几个特征,我们可以通过服务判断这个外设是不是我们要连接的外设,通过特征获取想要的数据。在特征有一个叫做UUID的属性,这个属性可以作为判断该特征是否是我们想要的特征的依据,这个跟硬件工程师要对接好UUID的值。

角色和职责

  • 中心设备与外围设备。中心设备搜索寻找信号,外围设备发送信号。
  • GATT服务端与GATT客户端。这决定了2个设备建立连接后如何通信。

假设有一个Android手机和一个BLE设备。手机扮演中心角色,设备扮演外围角色。
手机和外设建立连接后,他们相互传送GATT元数据(Metadata)。由传送数据的类型来决定谁做主机。比如,外设想要报告传感器数据给手机,那么外设来做服务端。如果外设要从手机接收更新信息,那么手机来做服务端。

仅当你需要时搜索设备

当你调用CBCenterManager类的 scanForPeripheralsWithServices:options: 方法搜索正在广播服务的外围设备时,你的中心设备会使用它的广播去监听所有正在广播的设备直到你明确停止它。

除非你需要搜索更多的设备,当你发现你要连接的设备时请停止搜索其他的设备。调用CBCenterManager类的stopScan方法去停止搜索其他设备。同见Connecting to a Peripheral Device After You’ve Discovered It

sbf282.com 2BLE之间的关系图.png

代码

在.h文件中定义一些全局属性:

//中心管理者@property (nonatomic, strong) CBCentralManager *centralManager;//外围设备@property (nonatomic, strong) CBPeripheral *peripheral;//读取设备信息的特征@property (nonatomic, strong) CBCharacteristic *readCharteristic;//收取消息的特征@property (nonatomic, strong) CBCharacteristic *notifyCharteristic;//发送消息的特征@property (nonatomic, strong) CBCharacteristic *writeCharacteristic;

首先,我们要创建一个中心管理者单例

 //queue中传入nil默认就是在主线程 self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil]; //设置代理 self.centralManager.delegate = self;

实例化一个中心管理者之后,会自动调用下面代理方法:

-centralManagerDidUpdateState:(CBCentralManager *)central{ switch (central.state) { case CBManagerStatePoweredOff:{ NSLog; break; case CBManagerStatePoweredOn:{ NSLog; [central scanForPeripheralsWithServices:nil options:nil]; } break; case CBManagerStateResetting: break; case CBManagerStateUnauthorized: NSLog(@"系统蓝未被授权"); break; case CBManagerStateUnknown: NSLog(@"系统蓝牙当前状态不明确"); break; case CBManagerStateUnsupported: NSLog(@"系统蓝牙设备不支持"); break; default: break; } }

这个代理方法是判断系统的蓝牙状态,如果蓝牙已经打开,则调用下面的代码:

 //在给services和options都传入nil则是代表扫描所有条件的外围设备 [central scanForPeripheralsWithServices:nil options:nil];

当扫描到外围设备后,会调用下面这个代理方法:(扫描到多少个外设就执行多少次)

- centralManager:(CBCentralManager *)central // 中心管理didDiscoverPeripheral:(nonnull CBPeripheral *)peripheral // 外设advertisementData:(nonnull NSDictionary<NSString *,id> *)advertisementData // 外设携带的数据 RSSI:(nonnull NSNumber *)RSSI// 外设发出的蓝牙信号强度{ //注意:这里可以获取到扫描到外设的Mac地址, NSString *mac = advertisementData[@"kCBAdvDataManufacturerData"] //这里也可以过滤掉不需要的服务 //接下来可以把我们想要连接的Mac地址与扫描到的外设的Mac地址进行匹配,如果一致就获取 if ([self.peripheralMac isEqualToString:mac]) { //这里就是我们要连接的外设 //获取外部设备 self.peripheral = peripheral; //用中心管理者去调用连接获取到的外设的方法 [self.centralManager connectPeripheral:peripheral options:nil]; //停止搜索 [self.centralManager stopScan]; }}

注意:代理方法是不会直接给我们返回外设的Mac地址的,我们可以通过advertisementData[@"kCBAdvDataManufacturerData"]去得到Mac地址,这个要跟硬件工程师沟通好怎么返回,以什么形式去返回。

连接成功后会自动执行下面这个代理方法:

- centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{ //获取到已经连接上的外设之后,设置代理 self.peripheral.delegate = self; //用外设去获取服务 [peripheral discoverServices:nil];}

如果连接失败,会调用下面这个方法:

- centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{ NSLog(@"连接失败%@",error.localizedDescription);}

如果两个已经上的设置突然断开了连接,会自动调用下面的方法:

- centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error //这种情况一般是,你蓝牙不稳定或者蓝牙断开等一些外部环境问题 NSLog(@"已经断开蓝牙连接");{

连接外设成功并调用获取外设服务的方法后,获取外设服务成功后会调用下面的方法:

- peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ for (CBService *service in peripheral.services){ NSLog(@"外设服务号:%@",service.UUID.UUIDString); if ([service.UUID.UUIDString isEqualToString:kServiceUUID]) { //外围设备查找指定服务中的特征,characteristics为nil,表示寻找所有特征 [peripheral discoverCharacteristics:nil forService:service]; break; } }}

当获取外设服务的特征成功后,自动执行下面的代理方法:在这个方法中,就根据

-peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{//kWriteCharacteristicUUID、kNotifyCharacteristicUUID、kReadCharacteristicUUID是与硬件对接好的特征的UUID, for (CBCharacteristic *characteristic in service.characteristics) { //写入特征 if ([characteristic.UUID.UUIDString isEqualToString:kWriteCharacteristicUUID] ) { self.writeCharacteristic = characteristic; } //通知特征 if ([characteristic.UUID.UUIDString isEqualToString:kNotifyCharacteristicUUID]) { self.notifyCharteristic = characteristic; /* 设置是否向特征订阅数据实时通知,YES表示会实时多次会调用代理方法读取数据 */ [peripheral setNotifyValue:YES forCharacteristic:characteristic]; } //读取特征 if ([characteristic.UUID.UUIDString isEqualToString:kReadCharacteristicUUID]) { self.readCharteristic = characteristic; /* 读取特征的数据,调用此方法后会调用一次代理方法读取数据 */ [peripheral readValueForCharacteristic:characteristic]; } }}

注意:kWriteCharacteristicUUID、kNotifyCharacteristicUUID、kReadCharacteristicUUID是与硬件对接好的特征的UUID,用来判断是不是我们想要的特征,这里分为写入特征、读取特征、通知特征,分别是代表app想设备发送信息、app从设备获取信息、app获取设备的信息。

来到这里就已经获取到我们想要的特征了,接下来我们开始最后一步,写入信息和读取信息了。下面的这个代理方法是,设备所有的数据都是从中这个代理方法返回的:

- peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ //从我们想要的特征中获取返回的数据 if ([self.notifyCharteristic isEqual:characteristic]) { NSLog(@"设备爱返回的数据:%@",characteristic.value); }}

当我们想向设备写入数据的时候,调用下面这个方法:

 if(self.characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) { //手机向外设发送数据,写数据 [self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];} 

注意:这里写入的data是一个二进制形式

写到这里就结束了,上面就是讲解了开头的6个步骤的实现方法,利用原生的api去封装一个蓝牙通讯的SDK不难,关键是这个蓝牙通讯的SDK结合自己的项目穿插各种逻辑的时候,就需要谨慎思考了。

谢谢~

BLE Permissions

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

如果要声明APP仅适用于BLE设备,在manifest中写

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

上面的代码中true改为false,就是不支持BLE设备

// 检查这台设备是否支持BLE
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, “本机不支持BLE”, Toast.LENGTH_SHORT).show();
    finish();
}

必要时指定CBCentralManagerScanOptionAllowDuplicatesKey选项

外围设备可能一秒发送多个广播包向监听的中心设备表明它们的存在。当你调用scanForPeripheralsWithServices:options:方法搜索设备时,该方法默认处理所有广播的外围设备并在一个事务里面处理,也就是说,中心设备每发现一个新的外围设备就会回调 centralManager:didDiscoverPeripheral:advertisementData:RSSI:方法,并且忽略很多接收的广播包。已搜索到的外围设备广播包发生变化时,中心设备也会回调这个方法。

如果你想要改变这种默认的处理方式,你应该在调用 scanForPeripheralsWithServices:options: 时,指定一个 CBCentralManagerScanOptionAllowDuplicatesKey 常量做为搜索选项。设定后每次中心设备从外围设备收到广播包都会触发发现设备回调。大多数时候关闭这种默认的处理行为是有用的,比如根据外围设备的信号强度建立近距离的连接(使用外围设备的信号强度值)。也就是说,记住一点指定一个搜索选项对电池的寿命和app性能是不利的。因此,仅在一些必要的情况下使用指定搜索选项。

我们一般的交互,是app作为客户端,而用户的实际数据多存储在服务器上,所以app客户端主动通过网络接口从服务器端获取数据,然后在app中展示这些数据。而蓝牙有一些不同,app是外设管理中心(CBCentralManager),但是它也是客户端。而实际的数据是从蓝牙外设(CBPeripheral),也就是蓝牙手环等这类设备中获取,所以CBPeripheral就相当于是服务器,与他们有些不同的是,蓝牙数据传输是服务器(CBPeripheral)一直在广播发送数据,app客户端连接监听某个蓝牙后,就会收到其发送过来的数据展示。蓝牙外设,不管有没有别的设备连接它,蓝牙外设都会广播发送数据。情景一 只涉及从蓝牙外设中读数据*蓝牙手环蓝牙手环一直往外广播发送心跳和走路的步数,当我们的app通过蓝牙连接到蓝牙手环后,就可以在外设的代理方法中,获取广播发出的数据了,然后在app的UI中更新数据即可。*情景二 往蓝牙外设中写数据 **蓝牙打印机蓝牙打印机是app中通过蓝牙连接到蓝牙打印机之后,利用外设的代理方法,往蓝牙打印机中写入数据后,蓝牙打印机就会自动打印出小票。情景三 两台iOS 设备通过app互传文件一台设备不能既是外设,又是管理中心。它可以既广播发送数据,又获取其他设备的数据,但是它只能扮演一种角色,如果iOS 设备A 通过蓝牙主动连接了 设备B,那么设备A是CBCentral,设备B是CBPeripheral;但是如果是设备B连接了设备A,那么设备B就是CBCentral,设备A是CBPeripheral

设置BLE

明智的搜索数据

外围设备可能提供很多服务和特征,而不只是你开发app为了完成指定任务感兴趣的。搜索外围设备所有的服务和特征对电池寿命和app性能是不利的。因此你应该搜索你app关心的那些服务和特征。

例如,假设你连接到一个提供很多服务的外围的设备,但是你的app只需要其中的两个服务。你可以通过使用包含服务UUIDs(用CBUUID对象)的数组,调用CBPeripheral类的discoverServices: 方法来指定搜索这两个服务。如下示例:

[peripheral discoverServices:@[firstServiceUUID, secondServiceUUID]];

在搜索到你感兴趣的两个服务之后,你可以同样的方式搜索服务里面你感兴趣的特征。再次简单的通过使用包含标识特征UUIDs的数组,调用CBPerpheral类的discoverCharacteristics:forService:的方法从服务里面搜索你需要的特征。

第一步,创建CBCentralManager。第二步,扫描可连接的蓝牙外设(必须在蓝牙模块打开的前提下)。第三步,连接目标蓝牙外设。第四步,查询目标蓝牙外设下的服务。第五步,遍历服务中的特性,获取特性中的数据或者保存某些可写的特性,或者设置某些特性值改变时,通知主动获取。第六步,在通知更新特性中值的方法中读取特性中的数据(再设置特性的通知为YES的情况下)。第七步,读取特性中的值。第八步,如果有可写特性,并且需要向蓝牙外设写入数据时,写入数据发送给蓝牙外设。

1.获取BluetoothAdapter

蓝牙app都需要BluetoothAdapter。

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

监听那些经常改变数据的特征

正如Retrieving the Value of a Characteristic描述的那样,有两种方式你可以接收特征的数据:

  • 每次你需要获取数据时,你可以明确的调用readValueForCharacteristic:方法获取特征数据。
  • 你可以调用setNotifyValue:forCharacteristic:方法监听特征数据,一旦外围设备特征数据发生变化你就会收到通知。

对于那些经常改变的特征数据,监听他们是一种良好的处理方式。同样关于如何监听特征的数据,参考 Subscribing to a Characteristic’s Value.

首先是是在我们app中,创建一个CBCentralManager:

2.激活蓝牙

private BluetoothAdapter mBluetoothAdapter;
...
// Ensures Bluetooth is available on the device and it is enabled. If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

获取所有需要的数据后与设备断开连接

当连接不在需要的时候,你可以与外围设备断开连接来减少你设备的广播占用。以下两种情况你应该与外围设备断开连接:

  • 所有你监听的特征数据已经停止发送通知。(你可以通过访问特征属性isNotifying的值来判断特征数据是否发送通知。)
  • 你已经从外围设备获取所有需要的数据。

在上面两种情况,请取消监听并与外围设备断开连接。你可以通过调用setNotifyValue:forCharacteristic:方法设置第一个参数为NO,来取消任何你对特征数据的监听。你可以调用CBCentralManager类的cancelPeripheralConnection:的方法来取消与外围设备的连接。如下:

[myCentralManager cancelPeripheralConnection:peripheral];

注意:cancelPeripheralConnection:方法是非阻塞的,并且任何CBPeripheral类的方法正在等待外围设备影响时,你试图断开连接可能不成功。因为其他app可能正在连接这个外围设备,取消一个本地连接并不能保证低层的物理连接已经断开。从你app的角度,这种断开被认为断开,并且中心设备调用回调centralManager:didDisconnectPeripheral:error: 方法。

// 1.创建管理中心,这里也可以设置子线程 CBCentralManager *manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];

寻找BLE设备

DeviceScanActivity.java
DeviceControlActivity.java
BluetoothLeService.java

DeviceScanActivity负责搜索BLE设备;选定设备后启动BluetoothLeService,并且所有BLE操作都在这里进行;
DeviceControlActivity提供UI,从BluetoothLeService发来的信息在这里显示

调用startLeScan(),当然现在有替代的方法了

请注意:

  • 一旦找到需要的设备,马上停止搜索

  • 不要循环搜索,并设置一个时间限制。循环搜索很耗电。

以下代码是启动和停止搜索功能

/**
 * Activity for scanning and displaying available BLE devices.
 */
public class DeviceScanActivity extends ListActivity {

    private BluetoothAdapter mBluetoothAdapter;
    private boolean mScanning;
    private Handler mHandler;

    // Stops scanning after 10 seconds.
    private static final long SCAN_PERIOD = 10000;
    ...
    private void scanLeDevice(final boolean enable) {
        if (enable) {
            // Stops scanning after a pre-defined scan period.
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mScanning = false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);

            mScanning = true;
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
        ...
    }
...
}

如果要搜索特定类型的外围设备,使用 startLeScan(UUID[], BluetoothAdapter.LeScanCallback)
提供一组特定的GATT services UUID对象

下面是BluetoothAdapter.LeScanCallback的实现

private LeDeviceListAdapter mLeDeviceListAdapter;
...
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi,
            byte[] scanRecord) {
        runOnUiThread(new Runnable() {
           @Override
           public void run() {
               mLeDeviceListAdapter.addDevice(device);
               mLeDeviceListAdapter.notifyDataSetChanged();
           }
       });
   }
};

请注意,不能同时搜索BLE设备和传统蓝牙设备。

重新连接外围设备

使用CoreBluetooth框架,有三种方式与外围重新建立连接。你可以:

  • 检索那些已经搜索到或之前连接过的外设列表,调用 retrievePeripheralsWithIdentifiers: 方法。如果你要找的在外设在这列表里,尝试去连接它。

  • 检索那些当前连接到系统的外设列表,调用retrieveConnectedPeripheralsWithServices:方法。如果你要找的外设在列表里面,连接它到你的app。

  • 调用scanForPeripheralsWithServices:options:方法搜索外设。如果发现,连接它。

你可能不希望每次重新连接外设时都去搜索同样的外围设备,这取决你的应用场景。相反,你可能先使用别的方式去重新建立连接。如下一个示例显示了关于使用上面方式重新连接外设的工作流程。

sbf282.com 3

流程图

注意:你尝试使用这几种重新连接方式,并遵循这些步骤操作,在你app实践过程之中可能不同。比如,你决定不使用第一种方式,或是尝试同时先使用两种。

创建完之后,就会调用一次CBCentralManagerDelegate的代理方法:

连接到GATT服务端

在GATT客户端进行这个操作

mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
// mGattCallback在上面写好了

下面是一个BLE服务示例

// A service that interacts with the BLE device via the Android BLE API.
public class BluetoothLeService extends Service {
    private final static String TAG = BluetoothLeService.class.getSimpleName();

    private BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;
    private String mBluetoothDeviceAddress;
    private BluetoothGatt mBluetoothGatt;
    private int mConnectionState = STATE_DISCONNECTED;

    private static final int STATE_DISCONNECTED = 0;
    private static final int STATE_CONNECTING = 1;
    private static final int STATE_CONNECTED = 2;

    public final static String ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
    public final static String ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
    public final static String ACTION_GATT_SERVICES_DISCOVERED =
            "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
    public final static String ACTION_DATA_AVAILABLE =
            "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
    public final static String EXTRA_DATA =
            "com.example.bluetooth.le.EXTRA_DATA";

    public final static UUID UUID_HEART_RATE_MEASUREMENT =
            UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);

    // Various callback methods defined by the BLE API.
    private final BluetoothGattCallback mGattCallback =
            new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status,
                int newState) {
            String intentAction;
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                intentAction = ACTION_GATT_CONNECTED;
                mConnectionState = STATE_CONNECTED;
                broadcastUpdate(intentAction);
                Log.i(TAG, "Connected to GATT server.");
                Log.i(TAG, "Attempting to start service discovery:" +
                        mBluetoothGatt.discoverServices());

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                intentAction = ACTION_GATT_DISCONNECTED;
                mConnectionState = STATE_DISCONNECTED;
                Log.i(TAG, "Disconnected from GATT server.");
                broadcastUpdate(intentAction);
            }
        }

        @Override
        // New services discovered
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }

        @Override
        // Result of a characteristic read operation
        public void onCharacteristicRead(BluetoothGatt gatt,
                BluetoothGattCharacteristic characteristic,
                int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
            }
        }
     ...
    };
...
}

这里值得注意的是,连接上之后不要马上进行读写characteristic的操作。要等onServicesDiscovered执行后再读写命令。

下面是发送广播的辅助方法

private void broadcastUpdate(final String action) {
    final Intent intent = new Intent(action);
    sendBroadcast(intent);
}

private void broadcastUpdate(final String action,
                             final BluetoothGattCharacteristic characteristic) {
    final Intent intent = new Intent(action);

    // This is special handling for the Heart Rate Measurement profile. Data
    // parsing is carried out as per profile specifications.
    if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
        int flag = characteristic.getProperties();
        int format = -1;
        if ((flag & 0x01) != 0) {
            format = BluetoothGattCharacteristic.FORMAT_UINT16;
            Log.d(TAG, "Heart rate format UINT16.");
        } else {
            format = BluetoothGattCharacteristic.FORMAT_UINT8;
            Log.d(TAG, "Heart rate format UINT8.");
        }
        final int heartRate = characteristic.getIntValue(format, 1);
        Log.d(TAG, String.format("Received heart rate: %d", heartRate));
        intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
    } else {
        // For all other profiles, writes the data formatted in HEX.
        final byte[] data = characteristic.getValue();
        if (data != null && data.length > 0) {
            final StringBuilder stringBuilder = new StringBuilder(data.length);
            for(byte byteChar : data)
                stringBuilder.append(String.format("%02X ", byteChar));
            intent.putExtra(EXTRA_DATA, new String(data) + "n" +
                    stringBuilder.toString());
        }
    }
    sendBroadcast(intent);
}

发送出来的广播在DeviceControlActivity中接收处理

// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a
// result of read or notification operations.
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
            mConnected = true;
            updateConnectionState(R.string.connected);
            invalidateOptionsMenu();
        } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
            mConnected = false;
            updateConnectionState(R.string.disconnected);
            invalidateOptionsMenu();
            clearUI();
        } else if (BluetoothLeService.
                ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
            // Show all the supported services and characteristics on the
            // user interface.
            displayGattServices(mBluetoothLeService.getSupportedGattServices());
        } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
            displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
        }
    }
};

检索已知外围设备列表

在你第一次搜索到一个外围设备时,系统就会为其生成一个ID(UUID,用NSUUID对象来表示)来标识这个外设。你可以保存这个标识(例如使用NSUserDefaults类),并且在以后调用CBCentralManager类的retrievePeripheralsWithIdentifiers:方法使用这个标识重新连接外设。如下描述了一种方式使用这种方法和之前连接过的外设建立重新连接。

在你的app启动之后,调用retrievePeripheralsWithIdentifiers:方法,使用一个包含之前已搜索到和连接过的外设id的数组,如下:

knownPeripherals = [myCentralManager retrievePeripheralsWithIdentifiers:savedIdentifiers];

中心设备尝试从之前已发现的外设中匹配此标识的外设,并返回一个包含CBPeripheral对象的数组。如果没有匹配找,这个数组将为空,你需要尝试其他两种重新连接方式。如果数组不为空,让用户选择哪个外设去建立重新连接。

当用户选择了外设,调用CBCentralManager类的connectPeripheral:options:方法去连接它。如果这个外设还可以被连接,中心设备会回调centralManager:didConnectPeripheral:,这个外面重新连接成功。

注意:一个外设可能由于一些原因不能被连接成功。对此,这个外面可能不在中心设备的周围。另外,一些蓝牙低功耗外外设周期的使用随机地址。因此,尽管一些外设在附近,这个外设的地址较上次发现之后已经发生了变化,这样你尝试使用这个CBPeripheral对象对连接它,其实产不是对应的外设。如果因为外设地址变化你无法重新连接,你必须使用scanForPeripheralsWithServices:options:方法重新搜索它。
更多关于随机外设地址的信息,请查看 the Bluetooth 4.0 specification, Volume 3, Part C, Section 10.8 and Bluetooth Accessory Design Guidelines for Apple Products.

- centralManagerDidUpdateState:(CBCentralManager *)central{ NSLog(@"%@",central); switch (central.state) { case CBCentralManagerStatePoweredOn: NSLog; [_manager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@}]; break; case CBCentralManagerStatePoweredOff: NSLog(@"可用,未打开"); break; case CBCentralManagerStateUnsupported: NSLog(@"SDK不支持"); break; case CBCentralManagerStateUnauthorized: NSLog; break; case CBCentralManagerStateResetting: NSLog(@"CBCentralManagerStateResetting"); break; case CBCentralManagerStateUnknown: NSLog(@"CBCentralManagerStateUnknown"); break; }}

读取BLE属性

Android APP连接上BLE服务端后,就可以发现服务,并能读写属性值。
下面是一个读属性的例子,它将服务端所有的属性都列出来

public class DeviceControlActivity extends Activity {
    ...
    // Demonstrates how to iterate through the supported GATT
    // Services/Characteristics.
    // In this sample, we populate the data structure that is bound to the
    // ExpandableListView on the UI.
    private void displayGattServices(List<BluetoothGattService> gattServices) {
        if (gattServices == null) return;
        String uuid = null;
        String unknownServiceString = getResources().
                getString(R.string.unknown_service);
        String unknownCharaString = getResources().
                getString(R.string.unknown_characteristic);
        ArrayList<HashMap<String, String>> gattServiceData =
                new ArrayList<HashMap<String, String>>();
        ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
                = new ArrayList<ArrayList<HashMap<String, String>>>();
        mGattCharacteristics =
                new ArrayList<ArrayList<BluetoothGattCharacteristic>>();

        // Loops through available GATT Services.
        for (BluetoothGattService gattService : gattServices) {
            HashMap<String, String> currentServiceData =
                    new HashMap<String, String>();
            uuid = gattService.getUuid().toString();
            currentServiceData.put(
                    LIST_NAME, SampleGattAttributes.
                            lookup(uuid, unknownServiceString));
            currentServiceData.put(LIST_UUID, uuid);
            gattServiceData.add(currentServiceData);

            ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
                    new ArrayList<HashMap<String, String>>();
            List<BluetoothGattCharacteristic> gattCharacteristics =
                    gattService.getCharacteristics();
            ArrayList<BluetoothGattCharacteristic> charas =
                    new ArrayList<BluetoothGattCharacteristic>();
           // Loops through available Characteristics.
            for (BluetoothGattCharacteristic gattCharacteristic :
                    gattCharacteristics) {
                charas.add(gattCharacteristic);
                HashMap<String, String> currentCharaData =
                        new HashMap<String, String>();
                uuid = gattCharacteristic.getUuid().toString();
                currentCharaData.put(
                        LIST_NAME, SampleGattAttributes.lookup(uuid,
                                unknownCharaString));
                currentCharaData.put(LIST_UUID, uuid);
                gattCharacteristicGroupData.add(currentCharaData);
            }
            mGattCharacteristics.add(charas);
            gattCharacteristicData.add(gattCharacteristicGroupData);
         }
    ...
    }
...
}

检索已连接的外围设备列表

另外一种重新连接外设的方式就是检查这个外设是否已经连接到系统上了(有可能是其他app连接的)。你可以调用CBCenteralManager类的retrieveConnectedPeripheralsWithServices:方法,将会返回一个包含CBPeripheral对象的数组,用这表示已经连接到系统的外设。

连接到当前系统的外设可能不此有一个,你可以通过使用一个包含CBUUID对象的数组来检索那些连接到当前系统的并且包含特定UUID服务的外设。如果没有外设连接到当前系统,数组为空并且你应该尝试其他两种重新连接方式。如果数组不为空,让用户选择一个外设尝试去连接它。

假设用户搜索然后选择了一个期望连接的外设,直接调用 CBCentralManager类的connectPeripheral:options:方法连接到你的app。(尽管这个外设已经连接到系统,你仍然需要连接它到你的app并与之交互。)当本地连接已经建立,中心设备会回调 centralManager:didConnectPeripheral:表示外设重新连接成功。

该代理方法,在蓝牙模板的状态发生改变的时候,就会回调。应该在蓝牙打开的状态下,再去搜索扫描可用的蓝牙外设列表。扫描蓝牙外设是通过如下方法:

本文由胜博发-编程发布,转载请注明来源:搜索成功并发现有可用的设备后sbf282.com:,所以