随着智能家具、智能穿戴等的兴起,蓝牙开发应用越来越广泛,有关蓝牙方面的问题,今天就给大家进行详细的讲解,想要了解蓝牙在iOS开发中的具体实现,首先我们必须知道什么是蓝牙?
蓝牙概念:
随着蓝牙低功耗技术BLE(Bluetooth Low Energy)的发展,蓝牙技术正在一步步成熟,如今的大部分移动设备都配备有蓝牙4.0,相比之前的蓝牙技术耗电量大大降低。从ios的发展史也不难看出苹果目前对蓝牙技术也是越来越关注,例如苹果于2013年9月发布的iOS7就配备了iBeacon技术,这项技术完全基于蓝牙传输。但是众所周知苹果的设备对于权限要求也是比较高的,因此在iOS中并不能像Android一样随意使用蓝牙进行文件传输(除非你已经越狱)。知道什么是蓝牙之后,那么在iOS中进行蓝牙传输应用开发常用的框架有哪几种呢?
蓝牙在开发中的框架
GameKit.framework:iOS7之前的蓝牙通讯框架,从iOS7开始过期,但是目前多数应用还是基于此框架。
MultipeerConnectivity.framework:iOS7开始引入的新的蓝牙通讯开发框架,用于取代GameKit。
CoreBluetooth.framework:功能强大的蓝牙开发框架,要求设备必须支持蓝牙4.0。
因为GameKit.framework和MultipeerConnectivity.framework框架都只能在iOS设备之间进行数据传输,这个框架最大的特点就是完全基于BLE4.0标准并且支持非iOS设备。当前BLE应用相当广泛,不再仅仅是两个设备之间的数据传输,它还有很多其他应用市场,例如室内定位、无线支付、智能家居等等,这也使得CoreBluetooth成为当前最热门的蓝牙技术。
今天我们只讲目前使用最多的一种蓝牙框架CoreBluetooth.framework,在做蓝牙开发之前,最好先了解一些概念:
外围设备和中央设备在CoreBluetooth中使用CBPeripheralManager和CBCentralManager表示。
CBPeripheralManager:外围设备通常用于发布服务、生成数据、保存数据。外围设备发布并广播服务,告诉周围的中央设备它的可用服务和特征。
CBCentralManager:中央设备使用外围设备的数据。中央设备扫描到外围设备后会就会试图建立连接,一旦连接成功就可以使用这些服务和特征。
外围设备和中央设备之间交互的桥梁是服务(CBService)和特征(CBCharacteristic),二者都有一个唯一的标识UUID(CBUUID类型)来唯一确定一个服务或者特征,每个服务可以拥有多个特征,下面是他们之间的关系:
服务(services):蓝牙外设对外广播的必定会有一个服务,可能也有多个,服务下面包含着一些特征,服务可以理解成一个模块的窗口;
特征(characteristic):存在于服务下面的,一个服务下面也可以存在多个特征,特征可以理解成具体实现功能的窗口,一般特征都会有value,也就是特征值,特征是与外界交互的最小单位;
UUID:可以理解成蓝牙上的唯一标识符(硬件上肯定不是这个意思,但是这样理解便于我们开发),为了区分不同的服务和特征,或者给服务和特征取名字,我们就用UUID来代表服务和特征。
蓝牙连接可以大致分为以下几个步骤
1.创建一个Central Manage中心设备管理器
2.搜索外围设备
3.连接外围设备
4.获得外围设备的服务
5.获得服务的特征
6.从外围设备读数据
7.给外围设备发送数据
首先我们先导入系统的BLE的框架 #import
必须遵守2个协议
/*中心管理者*/
@property(strong,nonatomic)CBCentralManager *CM;
/*连接到的外设*/
@property (nonatomic, strong)CBPeripheral *peripheral;
//1.创建一个Central Manage中心设备管理器并设置当前控制器视图为代理
_CM = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
#pragma mark - CBCentralManager代理方法
//2.中心服务器状态更新后
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
NSString *message = nil;
switch (central.state) {
case CBManagerStateUnknown:
message = @"该设备蓝牙状态未知,请检查系统设置";
break;
case CBManagerStateResetting:
message = @"该设备不支持蓝牙功能,请检查系统设置";
break;
case CBManagerStateUnsupported:
message = @"该设备不支持蓝牙功能,请检查系统设置";
break;
case CBManagerStateUnauthorized:
message = @"该设备蓝牙未授权,请检查系统设置";
break;
case CBManagerStatePoweredOff:
message = @"该设备尚未打开蓝牙,请在设置中打开";
break;
case CBManagerStatePoweredOn:
message = @"蓝牙已经成功开启";
//开始扫描外围设备
[self startScanPeripheralsWithServices];
break;
default:
message = @"该设备不支持蓝牙功能或者该设备尚未打开蓝牙,请检查系统设置";
break;
}
if(message != nil && message.length != 0) {
[MBProgressHUD showSuccess:message toView:nil];
}
}
#pragma mark - 扫描外围设备单独写一个方法
//扫描外围设备方法
- (void)startScanPeripheralsWithServices {
//NO表示不会重复搜索已经搜索到的设备
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:CBCentralManagerScanOptionAllowDuplicatesKey];
//当得知蓝牙为开启状态时,直接搜索周围的蓝牙设备
//开始搜索蓝牙
[self.CM scanForPeripheralsWithServices:nil options:options];
}
//2.发现外设后调用的方法
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {
/**
* 第一个参数:中心管理者
* 第二个参数:外围设备
* 第三个参数:外围设备携带的数据
* 第四个参数:外围设备发出的蓝牙信号强度
**/
//在此处对我们的advertisementData(外围设备携带的广播数据)进行一些处理
//把广播包advertisementData里的参数kCBAdvDataManufacturerData数据拼接Mac地址
NSString *facturerDataString = [[[[advertisementData[@"kCBAdvDataManufacturerData"] description] stringByReplacingOccurrencesOfString:@"<" withString:@""] stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];
NSString *macAddress = [facturerDataString substringFromIndex:4];
/**
** 需要对连接到的外围设备进行过滤(外围设备的名称或者Mac地址或者能区分的标示UUID都可以)
** 1.信号强度(40以上才连接,80以上连接)
** 2.通过设备名(设备字符串前缀是 OBand)
** 我们的过滤规则自定义:譬如:有******前缀并且信号强度大于**
** if ([peripheral.name hasPrefix:@"Tv221u-169E665D"])
** 通过打印,我们知道RSSI一般是带-的
**/
//此处设置需要的过滤规则
if ([macAddress isEqualToString:@"04a3169e665d"]) {
[self.CM stopScan];
//通常通过过滤,我们会得到一些外设,然后将外围设备储存到我们的可变数组中
//将符合要求的设备进行数据持久化,以便下面连接的时候使用
self.peripheral = peripheral;
//发现完之后就是进行连接
[self.CM connectPeripheral:self.peripheral options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];
}
}
//3.连接外围设备
//中心管理者连接外围设备成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
/**
* 第一个参数:中心管理者
* 第二个参数:外围设备
**/
//连接成功后,设置外围设备的代理
self.peripheral.delegate = self;
//中心管理者连接外围设备成功后停止扫以达到节省电量的目的
[self.CM stopScan];
//外围设备发现服务,传nil代表不过滤
//这里会触发外围设备的代理方法
//- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
[self.peripheral discoverServices:nil];
}
//外围设备连接失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
[MBProgressHUD showSuccess:@"外围设备连接失败" toView:nil];
}
//丢失连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
[MBProgressHUD showSuccess:@"外围设备丢失连接" toView:nil];
}
//4.获得外围设备的服务
#pragma mark - CBPeripheral Delegate
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
if (error) {
[MBProgressHUD showSuccess:@"搜索服务时发生错误" toView:nil];
DLog(@"搜索服务%@时发生错误:%@", peripheral.name, [error localizedDescription]);
return;
}
if ([peripheral isEqual:self.peripheral]) {
//便利获得的外围设备的服务
for (CBService *services in peripheral.services) {
[peripheral discoverCharacteristics:nil forService:services];
}
}
}
//5.获得服务的特征值
//发现外围设备服务里的特征值的时候调用的代理方法(这个是比较重要的方法,你在这里可以通过事先知道的UUID找到你需要的特征值、订阅特征,或者这里写入数据给特征也可以)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error {
if (error) {
[MBProgressHUD showSuccess:@"搜索特征时发生错误" toView:nil];
DLog(@"搜索特征%@时发生错误:%@", service.UUID, [error localizedDescription]);
return;
}
if ([peripheral isEqual:self.peripheral]) {
//新建特征值数组
NSArray *characteristics = [service characteristics];
CBCharacteristic *characteristic;
if ([[service UUID] isEqual:[CBUUID UUIDWithString:@"aaaaaaaaaa"]]) {
for (characteristic in characteristics) {
//发现特征
if ([[characteristic UUID] isEqual:[CBUUID UUIDWithString:@"bbbbbbbbbbb"]]) {
//记录特征
_cbchar = characteristic;
}
}
}
}
}
//订阅的特征值发生改变会调用此方法
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error {
NSData *data = characteristic.value;
NSString *characteristicValue = [[[[characteristic.value description] stringByReplacingOccurrencesOfString:@"<" withString:@""] stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""];
//根据蓝牙响应的数据去判断向特征下的蓝牙发送命令
if ([str isEqualToString:@"c c c c c c c c c c"]) {
if ([_cbchar.UUID isEqual:[CBUUID UUIDWithString:@"FFE9"]]) {
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:0];
int8_t byte0 = 0x7B;
[data appendBytes:&byte0 length:sizeof(byte0)];
int8_t byte1 = 0x00;
[data appendBytes:&byte1 length:sizeof(byte1)];
int8_t byte2 = 0x00;
[data appendBytes:&byte2 length:sizeof(byte2)];
int8_t byte3 = 0x01;
[data appendBytes:&byte3 length:sizeof(byte3)];
int8_t byte4 = 0x00;
[data appendBytes:&byte4 length:sizeof(byte4)];
int8_t byte5 = 0xFF;
[data appendBytes:&byte5 length:sizeof(byte5)];
int8_t byte6 = 0x7D;
[data appendBytes:&byte6 length:sizeof(byte6)];
//发送命令
[_peripheral writeValue:data forCharacteristic:_cbchar type:CBCharacteristicWriteWithResponse];
}
}else if ([str isEqualToString:@"dddddddddddd"]) {
if ([_cbchar.UUID isEqual:[CBUUID UUIDWithString:@"1111111111"]]) {
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:0];
int8_t byte0 = 0x7B;
[data appendBytes:&byte0 length:sizeof(byte0)];
int8_t byte1 = 0x00;
[data appendBytes:&byte1 length:sizeof(byte1)];
int8_t byte2 = 0x00;
[data appendBytes:&byte2 length:sizeof(byte2)];
int8_t byte3 = 0x01;
[data appendBytes:&byte3 length:sizeof(byte3)];
int8_t byte4 = 0x01;
[data appendBytes:&byte4 length:sizeof(byte4)];
int8_t byte5 = 0xFE;
[data appendBytes:&byte5 length:sizeof(byte5)];
int8_t byte6 = 0x7D;
[data appendBytes:&byte6 length:sizeof(byte6)];
//发送命令
[_peripheral writeValue:data forCharacteristic:_cbchar type:CBCharacteristicWriteWithResponse];
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
DLog(@"Error writing characteristic value: %@",
[error localizedDescription]);
}else {
DLog(@"发送数据成功");
[peripheral readValueForCharacteristic:characteristic];
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
DLog(@"更新特征值数据: %@ 错误: %@", characteristic.UUID,[error localizedDescription]);
}
//发送命令
if ([characteristicValue isEqualToString:@"123456789"]) {
//状态
}else if ([characteristicValue isEqualToString:@"987654321"]) {
//状态
}
}
补充说明:一个设备可以拥有不同的服务,一个服务下面可以对应不同的特征值 总体思路就是去找某一个服务下的某一个特征值,发送不同命令去操作 具体命令看硬件那边,iOS 不同安卓,mac地址一开始不像安卓那样直接可以获取,想要获取就要把地址放到广播包advertisementData里
如果有不明白的可以去看苹果官方API,解释的很清楚