备注:下面说到的内容都基于蓝牙4.0标准以上,主要以实践为主。
~ CoreBluetooth.framework 蓝牙4.0以低功耗著称,也称BLE。
~ Ios蓝牙设备主要是通过设备的服务(server)和特征 (Characteristic)来区别以及展示设备功能。
~当然蓝牙设备还分两种角色【 外设、中心设备】:
中心设备:手机去扫描连接外设,发现外设服务和属性,操作服务和属性的应用;
外设:蓝牙设备,比如智能手环之类的东西, 会由硬件工程师开发好,并定义好设备提供的服务,每个服务对于的特征,每个特征的属性(只读,只写,通知等等).
下面主要展示中心设备去扫描连接外设,并且通信的过程:
业务:
*蓝牙设备管理器 CBCentralManager *centralManager;
*特征1 CBCharacteristic *rxCharacteristic;//读特征
*特征2 CBCharacteristic *txCharacteristic;//写特征
*搜索到要连接的设备蓝牙 CBPeripheral *peripheral;
连接外设的代码实现流程:
1. 建立中心角色 (CBCentralManager)
2. 中心设备蓝牙状态更新 (centralManagerDidUpdateState)
2. 扫描外设(discover)
3. 连接外设(connect)
4. 扫描外设中的服务和特征(discover)
.4.1 获取外设的services
.4.2 获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值
5. 与外设做数据交互(explore and interact)
6. 订阅Characteristic的通知
7. 断开连接(disconnect)
干货来了:
//创建中心设备管理器并设置当前控制器视图为代理
_centralManager=[[CBCentralManager alloc]initWithDelegate:self queue:nil];
#pragma mark - CBCentralManager代理方法
//中心服务器状态更新后
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBPeripheralManagerStatePoweredOn:
LDLog(@"BLE已打开.");
//扫描外围设备
[self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:@“服务UUID”]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];
break;
default:
LDLog(@"此设备不支持BLE或未打开蓝牙功能,无法作为外围设备.");
break;
}}
/**
* 发现外围设备
* @param central 中心设备
* @param peripheral 外围设备
* @param advertisementData 特征数据
* @param RSSI 信号质量(信号强度)
*/
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
LDLog(@"发现外围设备...");
if(!peripheral){return;}
NSData *data = [advertisementData objectForKey:@"kCBAdvDataManufacturerData"];//广播报数据,根据硬件协议进行解析得到自己想要的数据
.......
//停止扫描
[self.centralManager stopScan];
self.peripheral = peripheral;
LDLog(@"开始连接外围设备...");
self.centralManager.delegate = self;
[self.centralManager connectPeripheral:self.peripheral options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];//这里以通知模式为例子(订阅模式...***文章后面会详细说)
}
//连接到外围设备
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
if([self.peripheral isEqual:peripheral]){
LDLog(@"连接外围设备成功!");
//设置外围设备的代理为当前视图控制器
peripheral.delegate=self;
//外围设备开始寻找服务
[peripheral discoverServices:@[[CBUUID UUIDWithString:@"服务UUID"]]];
}}
//连接外围设备失败
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
if([self.peripheral isEqual:peripheral]){
LDLog(@"连接外围设备失败!");
}}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
if([self.peripheral isEqual:peripheral]){
LDLog(@"断开外围连接设备!");
}}
#pragma mark - CBPeripheral 代理方法
//外围设备寻找到服务后
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
if([self.peripheral isEqual:peripheral]){
LDLog(@"已发现可用服务...");
if(error){
LDLog(@"外围设备寻找服务过程中发生错误,错误信息:%@",error.localizedDescription);
}
//遍历查找到的服务
CBUUID *serviceUUID=[CBUUID UUIDWithString:@“服务”];
CBUUID *txCharacteristicUUID=[CBUUID UUIDWithString:@“特征1”];
CBUUID *rxCharacteristicUUID=[CBUUID UUIDWithString:@“特征2”];
for (CBService *service in peripheral.services) {
if([service.UUID isEqual:serviceUUID]){
//外围设备查找指定服务中的特征
[peripheral discoverCharacteristics:@[txCharacteristicUUID,rxCharacteristicUUID] forService:service];
}}}}
//外围设备寻找到特征后
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if([self.peripheral isEqual:peripheral]){
LDLog(@"已发现可用特征...");
if (error) {
LDLog(@"外围设备寻找特征过程中发生错误,错误信息:%@",error.localizedDescription);
return;
}
//遍历服务中的特征
CBUUID *serviceUUID = [CBUUID UUIDWithString:@“服务”];
CBUUID *txCharacteristicUUID = [CBUUID UUIDWithString:@“特征1”];
CBUUID *rxCharacteristicUUID = [CBUUID UUIDWithString:@“特征2”];
if ([service.UUID isEqual:serviceUUID]) {
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:txCharacteristicUUID]) {
self.txCharacteristic = characteristic;
}
else if ([characteristic.UUID isEqual:rxCharacteristicUUID]){
self.rxCharacteristic = characteristic;
[peripheral setNotifyValue:YES forCharacteristic:self.rxCharacteristic];
//通知
/*找到特征后设置外围设备为已通知状态(订阅特征):
*1.调用此方法会触发代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
*2.调用此方法会触发外围设备的订阅代理方法
*/
//读取
// [peripheral readValueForCharacteristic:characteristic];
// if(characteristic.value){
// NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
// LDLog(@"读取到特征值:%@",value);
// }
}}}}}
//特征值被更新后
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if([self.peripheral isEqual:peripheral]){
LDLog(@"收到特征更新通知...");
if (error) {
LDLog(@"更新通知状态时发生错误,错误信息:%@",error.localizedDescription);
}
//给特征值设置新的值
CBUUID *txCharacteristicUUID=[CBUUID UUIDWithString:@“特征1”];
CBUUID *rxCharacteristicUUID=[CBUUID UUIDWithString:@“特征2”];
if ([characteristic.UUID isEqual:rxCharacteristicUUID]) {
if (characteristic.isNotifying) {
[self XXX]; // 这里是你特征值设置完状态更新后,你可以做一些你的操作...具体根据您的协议以及需求
if (characteristic.properties==CBCharacteristicPropertyNotify) {
LDLog(@"已订阅特征通知.");
return;
}
else if (characteristic.properties ==CBCharacteristicPropertyRead) {
//从外围设备读取新值,调用此方法会触发代理方法
[peripheral readValueForCharacteristic:characteristic];
}}
else{
LDLog(@"停止已停止.");
//取消连接
[self.centralManager cancelPeripheralConnection:peripheral];
}}
else if ([characteristic.UUID isEqual:txCharacteristicUUID]){
}}}
//更新特征值后(调用readValueForCharacteristic:方法或者外围设备在订阅后更新特征值都会调用此代理方法)
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if([self.peripheral isEqual:peripheral]){
if (error) {
LDLog(@"更新特征值时发生错误,错误信息:%@",error.localizedDescription);
return;
}
if (characteristic.value) {
NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
LDLog(@"读取到特征值:%@",value);
if([self.peripheral isEqual:peripheral]){
NSData *value=[characteristic value];
if(!value){return ;}
//解析蓝牙数据
NSString *sig = ...解析收到的蓝牙通知信息;
...再然后干你想干的事情
}}else{
LDLog(@"未发现特征值.");
}}}
//向外设写数据的结果回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error{
if([self.peripheral isEqual:peripheral]){
if (error) {
LDLog(@"更新特征值时发生错误,错误信息:%@",error.localizedDescription);
return;
}
else{
NSString *result =[[ NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
LDLog(@"写数据成功:%@",error.localizedDescription);
}}}
//停止扫描并断开连接
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
peripheral:(CBPeripheral *)peripheral{
//停止扫描
[self.centralManager stopScan];
//断开连接
[self.centralManager cancelPeripheralConnection:self.peripheral];
}
好了,现在要开启吐槽模式了:
首先,蓝牙开发这里有些小坑,当然大神是不会在意的~~~
坑1 :
如果您的APP需要在后台也能接收到外设发过来的数据,好吧,你需要在项目info.plist配置下,支持蓝牙后台数据交互;
坑2 :
虽然蓝牙只是短距离数据交互方式,但是也是一种长连接通信模式,当然那种连接上外设,干完事情立马断开的就不要往下看了...当APP后台切换前台的时候,这个时候很有必要查看一下您的蓝牙通信状态,首先手机蓝牙是否开启状态(用户喜欢动不动关闭蓝牙,觉得省电...),再检查蓝牙连接是否被关闭,最后根据我的辛酸史,最好向外设发一条数据,看一看外设是否有回应,这样子就完美了。
坑3: 关于蓝牙 数据读取模式的理解,咱们大Ios是在太人性化了...
其实蓝牙数据读取模式有三种:Notify ;Indicate ;Read;了解过安卓的肯定了解。。。但是伟大的ios系统把Notify 、Indicate 给合并了,貌似是个好事情~~~所以当您的蓝牙硬件工程师很牛掰的给你说 Notify模式,Indicate模式您可以一句话,我搞定了。
坑4:蓝牙的重连机制
前面提到了长连接,那么就有断开的情况,看清楚了这里说的‘断开’是指正常连接状态下,业务没有断开连接,因为其他因素(手机离外设距离远/手机或者外设蓝牙突然出问题了等。。)断开了连接,那么可以做一个自动重连的功能,怎么做呢,很简单接收到判断蓝牙断开就马上再次connect,,,对应的 要实现一个方法,那就是取消自动车重连的方法,并且还要考虑多台设备连接的情况,这就是看业务了~~~。
坑5:关于ios蓝牙主动调用断开问题
呵呵,安卓的蓝牙那可是 说断开就断开,一句话给开发者的感觉就是秒断;但是 ios不好意思了,连续断开的话10次有6次都会延迟(5秒左右吧,大家可以验证下,譬如 连续连接/断开几次试一下。。。),所以如果您有连续连接再断开的业务,那您就要注意了,可以手动延迟加标志量来处理。
坑...
等大伙来继续填了。。。