基本流程
1.将手机作为调试机,用来搜索附近的蓝牙设备(在本次练习中,指小米手环)并连接。所以,手机是中央设备central。
2.调试机先扫描周边蓝牙设备,用UITableview展示所扫描到的周边蓝牙设备。
3.将扫描到的蓝牙设备(小米手环)设置为蓝牙发射器,即是周边设备peripheral,进行连接connect。
4.连接上蓝牙设备(小米手环)后,获取蓝牙设备(小米手环)中的所有服务services。
5.对每一个服务service进行遍历,获取其中所有的特性characteristic。
6.读取每一个特性,获取每个特性的值value。
7.对蓝牙设备(小米手环)的指定服务中的指定特性写入数据,实现我们想要达到的效果。
1.扫描设备
新建一个project,我在最开始的ViewController中添加了一个UIButton,title为@“扫描蓝牙”,点击这个按钮会跳到下一个界面BLEViewController,在这个界面中开始扫描附近的蓝牙设备。
首先在BLEViewController中导入蓝牙框架头文件
#import
@interfaceBLEViewController ()<CBCentralManagerDelegate>
/**
* 用于管理中央设备的manager
*/
@property (nonatomic,strong)CBCentralManager *centralManager;
/**
* 用于保存扫描出来的周边设备的数组
*/
@property (nonatomic,strong)NSMutableArray *arrayBLE;
/**
* 用于处理蓝牙事件的线程
*/
@property (nonatomic)dispatch_queue_t bleGCD;
//初始化周边设备数组
self.arrayBLE = [[NSMutableArray alloc]init];
//初始化蓝牙线程
self.bleGCD =dispatch_queue_create("BLEgcd",NULL);
//初始化中央设备管理者
self.centralManager = [[CBCentralManager alloc]initWithDelegate:self queue:self.bleGCD];
实现CBCentralManagerDelegate中的代理方法centralManagerDidUpdateState,该方法会监听centralManager的状态变化,当运行程序的设备处在蓝牙开启状态下才开始扫描附近的蓝牙设备。
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state)
{
caseCBCentralManagerStatePoweredOn:
//当前运行程序的设备处于蓝牙开启状态,开始扫描附近的蓝牙设备
[self.centralManager scanForPeripheralsWithServices:niloptions:nil];
break;
default:
NSLog(@"central Manager Did change State");
break;
}
}
- (void)scanForPeripheralsWithServices:(nullableNSArray<CBUUID *> *)serviceUUIDs options:(nullableNSDictionary<NSString *,id> *)options;这个方法会一直不停地调用,在这个方法中,services参数可以传入包含CBUUID的数组,以便只扫描到拥有指定service的蓝牙设备;options参数可以传入包含CBCentralManagerScanOptionAllowDuplicatesKey或
CBCentralManagerScanOptionSolicitedServiceUUIDsKey的字典,前一个键表示是否过滤每次扫描中发现的同一蓝牙设备,后一个键对应的值可以是一个包含CBUUID的数组,以便只扫描指定service,我感觉似乎和本方法第一个参数services有点重复?这里想扫描到附近的所有蓝牙设备,所以都传入nil。
开始扫描之后,当扫描到蓝牙设备时,会来到下面这个方法。
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{
BLEinfo * discoveredBLEinfo = [[BLEinfoalloc] init];
//通过discoveredPeripheral这个属性保存了扫描到的peripheral,避免peripheral在当前方法结束后被释放
discoveredBLEinfo.discoveredPeripheral = peripheral;
discoveredBLEinfo.rssi = RSSI;
//更新 tableview数据源
[self saveBLE:discoveredBLEinfo];
}
BLEinfo是新建的工具类,用来保存扫描发现的蓝牙设备信息,具体如下:
#import
#import
@interface BLEinfo : NSObject
/**
* 扫描发现的蓝牙设备
*/
@property (nonatomic,strong)CBPeripheral *discoveredPeripheral;
/**
* 该蓝牙设备的信号质量,可以用来判断其距离中央设备的远近
*/
@property (nonatomic,strong)NSNumber *rssi;
@end
保存扫描中发现的蓝牙设备信息,并将它们展示到UITableView上:
- (BOOL)saveBLE:(BLEinfo *)discoveredBLEinfo{
for (BLEinfo * infoin self.arrayBLE) {
if ([info.discoveredPeripheral.identifier.UUIDStringisEqualToString:discoveredBLEinfo.discoveredPeripheral.identifier.UUIDString]) {
return NO;
}
}
[self.arrayBLEaddObject:discoveredBLEinfo];
[self.tableViewreloadData];
return YES;
}
扫描到的蓝牙设备展示如下:
2.连接设备
点击小米手环所在的cell,进入下一个页面BLEInfoViewController,在这个页面中开始连接小米手环。需要注意的是,要把上个页面中的centralManager传入到这个页面来用,并且重新设置delegate:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
BLEinfo * info = [self.arrayBLEobjectAtIndex:indexPath.row];
BLEInfoViewController * BLEInfo = [[BLEInfoViewControlleralloc] init];
BLEInfo.centralManager =self.centralManager;
BLEInfo.discoveredPeripheral = info.discoveredPeripheral;
[self.navigationControllerpushViewController:BLEInfo animated:YES];
}
用来展示当前连接上的蓝牙设备的service和characteristic的UITableViewController:
#import
#import
@interface BLEInfoViewController :UITableViewController<CBPeripheralDelegate,CBPeripheralManagerDelegate,CBCentralManagerDelegate>
/**
* 用于管理中央设备的manager
*/
@property (nonatomic,strong)CBCentralManager *centralManager;
/**
* 被发现的周边设备
*/
@property (nonatomic,strong)CBPeripheral *discoveredPeripheral;
/**
* tableview sections,保存蓝牙设备里面的services
*/
@property (nonatomic,strong)NSMutableArray *arrayServices;
/**
* tableview rows,保存蓝牙设备里面每一个service中的每一个特性与对应的值
*/
@property (nonatomic,strong)NSMutableArray *arrayCharacteristics;
/**
* 用来记录有多少特性,当所有特性保存完毕,刷新表格
*/
@property (atomic,assign)int characteristicNum;
@end
把上个页面中的centralManager传入到这个页面来用,并且重新设置delegate:
- (void)viewDidLoad {
[superviewDidLoad];
[self.centralManagersetDelegate:self];
if (self.discoveredPeripheral) {
//开始连接蓝牙设备
[self.centralManagerconnectPeripheral:self.discoveredPeripheraloptions:nil];
}
self.arrayServices = [[NSMutableArrayalloc] init];
self.arrayCharacteristics = [[NSMutableArrayalloc] init];
self.characteristicNum =0;
}
成功连接到蓝牙设备时会来到下面这个方法:
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
//与周边设备连接成功时调用的代理方法
//清空原先的服务数组
[self.arrayServicesremoveAllObjects];
//清空原先的特征数组
[self.arrayCharacteristicsremoveAllObjects];
//设置蓝牙设备的代理
[self.discoveredPeripheralsetDelegate:self];
//开始发现当前蓝牙设备中的服务
[self.discoveredPeripheraldiscoverServices:nil];
}
3.获取服务
查找到服务之后会来到下面这个方法,在这个方法中把蓝牙设备中的服务保存到数组中
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
//成功搜索到蓝牙设备的服务时调用的方法
if (error) {
NSLog(@"didDiscoverServices error : %@",error.localizedDescription);
return;
}
//将蓝牙设备中的服务保存到数组中
for (CBService * servicein peripheral.services) {
NSLog(@"Service found with UUID : %@",service.UUID);
NSMutableDictionary * dic = [[NSMutableDictionaryalloc] initWithDictionary:@{SECTION_NAME:service.UUID.description}];
[self.arrayServicesaddObject:dic];
//开始发现当前服务中的特性
[service.peripheraldiscoverCharacteristics:nilforService:service];
}
}
4.获取特性
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
//成功搜索到蓝牙设备服务中的特性时调用的方法
if (error) {
NSLog(@"didDiscoverCharacteristicsForService error %@",error.localizedDescription);
return;
}
NSMutableArray * characteristics = [[NSMutableArrayalloc] init];
for (CBCharacteristic * cin service.characteristics) {
self.characteristicNum ++;
//读取每个特性对应的值
[peripheral readValueForCharacteristic:c];
[characteristics addObject:c];
}
[self.arrayCharacteristicsaddObject:characteristics];
}
5.读取特性值
在下面这个方法中可以通过characteristic.value获取到每个特性的值,类型是NSData。
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//获取到特性值时调用的方法
if (error) {
NSLog(@"didUpdateValueForCharacteristic error %@",error.localizedDescription);
return;
}
self.characteristicNum --;
if (self.characteristicNum ==0) {
//所有服务中的特征值已读取完毕,更新表格
[self.tableViewreloadData];
}
}
6.重新连接
可能是小米手环中硬件设置的原因,蓝牙连接成功后30秒左右就会断开,需要在下面这个方法中重新进行蓝牙连接。
-(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"%s",__FUNCTION__);
//与周边设备连接断开时调用的代理方法
//清空原先的服务数组
[self.arrayServicesremoveAllObjects];
//清空原先的特征数组
[self.arrayCharacteristicsremoveAllObjects];
//刷新表格,避免在连接断开时滑动表格出现的崩溃
[self.tableViewreloadData];
//重新与周边设备建立连接
[central connectPeripheral:peripheral options:nil];
}
7.写入数据
在获取到的小米手环的service中,最后一个service的UUID是0x1802,代表“Immediate Alert”。该service中有一个characteristic的UUID是0x2A06,代表“Alert level”。对这个characteristic写入不同的数值,可以使小米手环有不同的效果。点击这个characteristic所在的cell,进入下一个页面XiaoMiViewController,在这个页面中开始控制小米手环。需要注意的是,要把上个页面中的centralManager传入到这个页面来用,并且重新设置delegate:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSMutableArray * characteristics = [self.arrayCharacteristicsobjectAtIndex:indexPath.section];
CBCharacteristic * characteristic = [characteristicsobjectAtIndex:indexPath.row];
if ([characteristic.UUID.descriptionisEqualToString:@"2A06"]) {
XiaoMiViewController * XiaoMi = [[XiaoMiViewControlleralloc] init];
XiaoMi.centralManager =self.centralManager;
XiaoMi.discoveredPeripheral =self.discoveredPeripheral;
[self.navigationControllerpushViewController:XiaoMi animated:YES];
}
}
- (void)viewDidLoad {
[superviewDidLoad];
self.title =@"小米手环";
[self.centralManagersetDelegate:self];
}
-(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
//重新与周边设备建立连接
[central connectPeripheral:peripheral options:nil];
}
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
//设置蓝牙设备的代理
[self.discoveredPeripheralsetDelegate:self];
//开始发现当前蓝牙设备中的服务
[self.discoveredPeripheraldiscoverServices:nil];
}
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
//开始发现当前蓝牙设备中每个服务里的每个特性
for (CBService * servicein peripheral.services) {
[service.peripheraldiscoverCharacteristics:nilforService:service];
}
}
- (IBAction)weakShock {
for ( CBService *servicein self.discoveredPeripheral.services ) {
if ([service.UUID.descriptionisEqual:@"1802"]) {
for ( CBCharacteristic *characteristic in service.characteristics ) {
if ([characteristic.UUID.descriptionisEqual:@"2A06"]) {
/* EVERYTHING IS FOUND, WRITE characteristic ! */
//将int转成NSData
int i = 1;
NSData *data = [NSDatadataWithBytes: &i length: sizeof(i)];
[self.discoveredPeripheralwriteValue:data forCharacteristic:characteristictype:CBCharacteristicWriteWithoutResponse];
}
}
}
}
}