蓝牙开发总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wj610671226/article/details/80415029

蓝牙开发总结

最近项目中的一个功能需要手机通过蓝牙设备与硬件通讯,先就本篇文章记录一下开发过程中遇到的问题以及上架注意事项。本篇文章的代码是把蓝牙作为中心模式去连接外设设备使用的。

遇到的问题

  • 自定义蓝牙权限未开启提醒

系统的提示只有在运行使用时候会提醒权限,如果此时用户没有去开启蓝牙,在不重启应用的情况下是不会在提醒第二次的。

解决办法:初始化蓝牙的时候关闭系统弹窗,在需要检测蓝牙权限的地方弹出提示框

self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_queue_create("coreBluetooh", DISPATCH_QUEUE_CONCURRENT)];

    // 关闭系统权限提示
//    self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_queue_create("coreBluetooh", DISPATCH_QUEUE_CONCURRENT) options:@{CBCentralManagerOptionShowPowerAlertKey: @(NO)}];
  • 在不使用的蓝牙的时候尽量断开连接,停止扫描,节省资源
  • 上架审核问题,尽量在iTunes Store中提交审核的时候,描述清楚蓝牙的作用,最好录制个演示视频,放在youtobe上面,其他视频网站笔者没有试过,但是其他平台大多都有广告?
  • 蓝牙和硬件通讯的时候,一定要熟悉约定的数据协议,这个是重点,当时对协议看了个大概,坑苦了自己。
  • 跳转设置页面开启蓝牙权限的操作

    • 第一次审核过了,第二次没过?。哎。。。o(╥﹏╥)o
      被拒信息以及解决办法:
      Guideline 2.5.1 - Performance - Software Requirements
      Your app uses the “prefs:root=” non-public URL scheme, which is a private entity. The use of non-public APIs is not permitted on the App Store because it can lead to a poor user experience should these APIs change.
      Continuing to use or conceal non-public APIs in future submissions of this app may result in the termination of your Apple Developer account, as well as removal of all associated apps from the App Store.
      Next Steps
      To resolve this issue, please revise your app to provide the associated functionality using public APIs or remove the functionality using the “prefs:root” or “App-Prefs:root” URL scheme.
    NSURL * url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
    if ([[UIApplication sharedApplication]canOpenURL:url]) {
        NSURL * blueTootnUrl = [NSURL URLWithString:@"App-Prefs:root=Bluetooth"];
        [[UIApplication sharedApplication]openURL:blueTootnUrl];
    }
    
    • 第二种骚操作,把App-Prefs:root=Bluetooth转成16进制,已上架?
    unsigned char ascll[]  = {0x41, 0x70, 0x70, 0x2d, 0x50, 0x72, 0x65, 0x66, 0x73, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x3d, 0x42, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68};
    NSData * data = [NSData dataWithBytes:ascll length:24];
    NSString * urlString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSURL * url = [NSURL URLWithString:urlString];
    if ([[UIApplication sharedApplication]canOpenURL:url]) {
        [[UIApplication sharedApplication]openURL:url];
    }
    
    • 第三种操作,是我从其他APP上面看到的灵感,做一个引导图指导开启蓝牙
      这里写图片描述
  • 最后上示例代码

#import <CoreBluetooth/CoreBluetooth.h>

// 协议数据包类型
typedef NS_ENUM(NSInteger, PackageEnum)
{
    PackageEnumEF1 = “省略”,
    PackageEnumEF2 = “省略”,
    PackageEnumEF3 = ”省略“,
    PackageEnumEF4 = “省略”,
};

NSInteger const KpackageDataLength = 20; // 协议数据包长度

@interface ViewController ()<CBCentralManagerDelegate, CBPeripheralDelegate>
@property(nonatomic, strong)NSMutableArray * peripherals; // 保存被发现的设备
@property(nonatomic, strong)CBCentralManager * manager; // 蓝牙设备管理对象
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 使用系统默认的提示框
    self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_queue_create("coreBluetooh", DISPATCH_QUEUE_CONCURRENT)];

    // 关闭系统权限提示
//    self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_queue_create("coreBluetooh", DISPATCH_QUEUE_CONCURRENT) options:@{CBCentralManagerOptionShowPowerAlertKey: @(NO)}];
    self.peripherals = [NSMutableArray array];
}

#pragma mark - CBCentralManagerDelegate
// 状态已经更新
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    switch (central.state) {
        case CBManagerStateUnknown:
            NSLog(@"CBManagerStateUnknown");
            break;
        case CBManagerStateResetting:
            NSLog(@"CBManagerStateResetting");
            break;
        case CBManagerStateUnsupported:
            NSLog(@"CBManagerStateUnsupported");
            break;
        case CBManagerStateUnauthorized:
            NSLog(@"CBManagerStateUnauthorized");
            break;
        case CBManagerStatePoweredOff:
            NSLog(@"CBManagerStatePoweredOff");
            break;
        case CBManagerStatePoweredOn:
            NSLog(@"CBManagerStatePoweredOn");
            // 开始扫描
            [self.manager scanForPeripheralsWithServices:nil options:nil];
            break;
        default:
            break;
    }
}

// 状态将要恢复
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary<NSString *,id> *)dict {
    NSLog(@"状态将要恢复");
}

// 发现外设
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
    if (peripheral.name.length == 0) { return; }
    // 一个主设备最多能连7个外设,每个外设最多只能给一个主设备连接,连接成功,失败,断开会进入各自的委托
    // 找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!!
    //连接设备
    if ([peripheral.name containsString:@"your rule"]) {
        // 找到对应设备停止扫描
        [central stopScan];
        [self.peripherals addObject:peripheral];
        NSLog(@"name = %@", peripheral.name);
        [self.manager connectPeripheral:peripheral options:nil];
    }

}

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

// 已经断开外设
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    NSLog(@"设备断开 - %@  -  断开原因 - %@", peripheral.name, error.localizedDescription);
    // 停止扫描
//    [central stopScan];
    // 断开连接
    [central cancelPeripheralConnection:peripheral];
}

// 已经连接到外设
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    NSLog(@"连接设备成功 - %@  - 成功", peripheral.name);
    [peripheral setDelegate:self];
    [peripheral discoverServices:nil];
}

#pragma mark - CBPeripheralDelegate
// 扫描服务
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    NSLog(@"扫描到服务%@, name = %@", peripheral.services, peripheral.name);
    if (error) {
        NSLog(@"扫描服务错误%@", error.localizedDescription);
        return;
    }

    // 遍历外设提供的服务
//    for (CBService * service in peripheral.services) {
//        NSLog(@"服务 - UUID = %@", service.UUID);
//        // 扫描每个service的Characteristics,扫描到后会进入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
//        [peripheral discoverCharacteristics:nil forService:service];
//    }

    // 项目中采用的服务 <CBService: 0x170262180, isPrimary = YES, UUID = FFE0>
    [peripheral discoverCharacteristics:nil forService:peripheral.services.lastObject];
}

// 扫描到Characteristics 发现特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    if (error) {
        NSLog(@"扫描到Characteristics失败%@", error.localizedDescription);
        return;
    }

    // 获取Characteristic的值,读到数据会进入方法 didUpdateValueForCharacteristic
    for (CBCharacteristic * characteristic in service.characteristics) {
        NSLog(@"扫描特征 - characteristics UUID : %@ -- service UUID :%@", characteristic.UUID, service.UUID);
        // 订阅通知 数据通知会进入:didUpdateValueForCharacteristic方法
        [peripheral setNotifyValue:YES forCharacteristic:characteristic];
//        [peripheral readValueForCharacteristic:characteristic];
    }

    // //搜索Characteristic的Descriptors 读到数据会进入方法didDiscoverDescriptorsForCharacteristic
//    for (CBCharacteristic * characteristic in service.characteristics){
//        [peripheral discoverDescriptorsForCharacteristic:characteristic];
//    }

    // 发送数据 
    [self writeCharacteristic:peripheral characteristic:service.characteristics.lastObject value:@“value”];;
}

// 打印Characteristic, 读取数据
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    // 根据数据处理
    // !注意,value的类型是NSData,具体开发时,会根据外设协议制定的方式去解析数据
    NSString * value = [[NSString alloc] initWithData:characteristic.value encoding:NSASCIIStringEncoding];

}

// 搜索到Characteristic的Descriptors
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    //打印出Characteristic和他的Descriptors
    NSLog(@"characteristic uuid:%@ ,  thread = %@", characteristic.UUID, [NSThread currentThread]);
    for (CBDescriptor * d in characteristic.descriptors) {
        NSLog(@"Descriptor uuid:%@",d.UUID);
    }
}

//获取到Descriptors的值
//- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
//    //打印出DescriptorsUUID 和value
//    //这个descriptor都是对于characteristic的描述,一般都是字符串,所以这里我们转换成字符串去解析
//    NSLog(@" 获取到Descriptors的值 === characteristic uuid:%@  value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
//}

// 写入数据的回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (error) {
        NSLog(@"写入数据失败");
        return;
    }
    NSLog(@"写入成功");
}

//- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
//    if (!error) {
//        NSLog(@"NotificationState = %@", characteristic.value);
////        [peripheral readValueForCharacteristic:characteristic];
//    } else {
//        NSLog(@"error = %@", error.localizedDescription);
//    }
//}

#pragma mark - 写入数据
- (void)writeCharacteristic:(CBPeripheral *)peripheral
             characteristic:(CBCharacteristic *)characteristic
                      value:(NSData *)value{
    //只有 characteristic.properties 有write的权限才可以写
    if(characteristic.properties & CBCharacteristicPropertyWrite) {
        [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
        NSLog(@"开始写入数据");
    } else {
        NSLog(@"该设备不可写入");
    }
}


#pragma mark - 组织数据
- (void)makeData:(NSString *)str type:(PackageEnum)type{

}
  • 我的Demo中通讯结果展示
    这里写图片描述

补充: 从网上找了一个跳转设置表记录以备后用

跳转目标 对应字符串
无线局域网 App-Prefs:root=WIFI
蓝牙 App-Prefs:root=Bluetooth
蜂窝移动网络 App-Prefs:root=MOBILE_DATA_SETTINGS_ID
个人热点 App-Prefs:root=INTERNET_TETHERING
运营商 App-Prefs:root=Carrier
通知 App-Prefs:root=NOTIFICATIONS_ID
通用 App-Prefs:root=General
通用-关于本机 App-Prefs:root=General&path=About
通用-键盘 App-Prefs:root=General&path=Keyboard
通用-辅助功能 App-Prefs:root=General&path=ACCESSIBILITY
通用-语言与地区 App-Prefs:root=General&path=INTERNATIONAL
通用-还原 App-Prefs:root=Reset
墙纸 App-Prefs:root=Wallpaper
Siri App-Prefs:root=SIRI
隐私 App-Prefs:root=Privacy
Safari App-Prefs:root=SAFARI
音乐 App-Prefs:root=MUSIC
音乐-均衡器 App-Prefs:root=MUSIC&path=com.apple.Music:EQ
照片与相机 App-Prefs:root=Photos
FaceTime App-Prefs:root=FACETIME

猜你喜欢

转载自blog.csdn.net/wj610671226/article/details/80415029