iOS开发硬件交互-蓝牙模式

关于

  近期都在作一些与智能硬件交互的项目,从公司的项目走向,能够明显的感受到愈来愈多的家电,医疗器械,家居用品公司开始借助手机APP来帮助他们实现自家产品的“智能化”,优化用户体验。相信随着研发技术的提高和研发成本的下降,这种智能软硬件结合的产品将会迅速普及开来。git

从目前APP同硬件模块通讯的方式来看无非分为如下几种:github

  • 蓝牙链接模式
  • WiFi 链接模式(Socket 或 HTTP server)
  • DLNA 音视频共享 (iOS端还可以使用 AirPlay)

这篇随笔是对近期项目技术点的一个简单的回顾总结。框架


蓝牙链接模式

蓝牙基础知识

  • iOS平台下咱们可使用 MFI(ExternalAccessory 框架) 或 BLE (CoreBluetooth 框架) 进行蓝牙开发,但实际中咱们基本使用 CoreBluetooth 框架,由于它功能更强大,支持蓝牙4.0标准。测试

  • CoreBluetooth 框架的核心是 peripheral 和 central, 能够理解成外设和中心,发起链接的是 central,被链接的设备为 peripheral,它们是一组相对概念。好比,当手机去链接控制蓝牙耳机时,你的手机就是 central,当手机蓝牙被另外一个手机链接并为其提供服务时就是 peripheral。优化

  • CoreBluetooth 框架分别为“中心模式”和“外设模式”提供一组API。在移动端开发中,咱们一般用到的中心模式。ui

  • Service 和 Characteristic:蓝牙设备经过GATT协议定义的数据通信方式。一个 peripheral 能够能够提供多种 Service,一种 Service 又能够包含多个不一样的 Characteristic。central 经过 peripheral 的 Characteristic 来读写外设的数据,和获取通知。
    spa

蓝牙的两种工做模式

一、中心模式
1. 创建中心
2. 扫描外设(discover)
3. 链接外设(connect)
4. 扫描外设中的服务和特征(discover)
    - 4.1 获取外设的 services
    - 4.2 获取外设的 Characteristics,获取Characteristics的值,获 Characteristics的 Descriptor 和 Descriptor 的值
5. 与外设作数据交互(explore and interact)
6. 订阅 Characteristic 的通知
7. 断开链接(disconnect)
二、外设模式
1. 启动一个 Peripheral 管理对象
2. 本地 Peripheral 设置服务,特性,描述,权限等等
3. Peripheral 发送广播
4. 设置处理订阅、取消订阅、读 characteristic、写 characteristic 的委托方法
3.蓝牙设备的工做状态
1. 准备(standby)
2. 广播(advertising)
3. 监听扫描(Scanning
4. 发起链接(Initiating)
5. 已链接(Connected)

代码演示


#import "ViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>

@interface ViewController ()<CBCentralManagerDelegate,CBPeripheralDelegate>
{
    CBCentralManager *_centralManager; //central 管理器 (中心模式)
    NSMutableArray   *_peripherals; //peripheral 外设集
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    _peripherals = [NSMutableArray array];
    
    //初始化并设置委托和线程队列
    _centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue() options:nil];
}


#pragma mark - CBCentralManagerDelegate
// _centralManager 初始化状态
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    NSLog(@"%s -- %ld", __func__ ,(long)central.state);
    
//    typedef NS_ENUM(NSInteger, CBCentralManagerState) {
//        CBCentralManagerStateUnknown = 0,
//        CBCentralManagerStateResetting,
//        CBCentralManagerStateUnsupported,
//        CBCentralManagerStateUnauthorized,
//        CBCentralManagerStatePoweredOff,
//        CBCentralManagerStatePoweredOn,
//    };
    
    //手机蓝牙状态判断
    switch (central.state) {
        case CBCentralManagerStateUnknown:
            NSLog(@"central state >> CBCentralManagerStateUnknown");
            break;
        case CBCentralManagerStateResetting:
            NSLog(@"central state >> CBCentralManagerStateResetting");
            break;
        case CBCentralManagerStateUnsupported:
            NSLog(@"central state >> CBCentralManagerStateUnsupported");
            break;
        case CBCentralManagerStateUnauthorized:
            NSLog(@"central state >> CBCentralManagerStateUnauthorized");
            break;
        case CBCentralManagerStatePoweredOff:
            NSLog(@"central state >> CBCentralManagerStatePoweredOff");
            break;
        case CBCentralManagerStatePoweredOn:
        {
            NSLog(@"central state >> CBCentralManagerStatePoweredOn");
            //开始扫描 peripheral
            //if <i>serviceUUIDs</i> is <i>nil</i> all discovered peripherals will be returned.
            [_centralManager scanForPeripheralsWithServices:nil options:nil];
            break;
        }
        default:
            break;
    }
}

// 发现蓝牙外设 peripheral
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{
//    NSLog(@"%s -- %@ RRSI:%ld", __func__ ,peripheral.name,(long)RSSI.integerValue);
    
    //链接全部 “Bioland” 名前缀的 peripheral
    if ([peripheral.name hasPrefix:@"Bioland-BGM"]) {
        //一个主设备最多能连7个外设,但外设最多只能给一个主设备链接,链接成功,失败,断开会进入各自的委托
        
        //找到的设备必须持有它,不然CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!
        [_peripherals addObject:peripheral];
        [_centralManager connectPeripheral:peripheral options:nil];
    }
}

// 中心设备(central) 链接到 外设(peripheral)
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    NSLog(@"链接到 外设(peripheral) -- %@" ,peripheral.name);
    
    //设置的peripheral委托CBPeripheralDelegate
    [peripheral setDelegate:self];
    //扫描外设Services,成功后会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
    [peripheral discoverServices:nil];
}

// 外设链接失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@"%s -- %@", __func__ ,peripheral.name);

}

// peripheral 断开链接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
    NSLog(@"%s -- %@", __func__ ,peripheral.name);

}




#pragma mark - CBPeripheralDelegate
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
      NSLog(@"扫描到服务:%@",peripheral.services);
    if (error)
    {
        NSLog(@"Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
        return;
    }
    
    for (CBService *service in peripheral.services) {
        NSLog(@"%@",service.UUID);
        if ([service.UUID.UUIDString isEqualToString:@"1000"]) {
        //扫描每一个service的Characteristics,扫描到后会进入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
        [peripheral discoverCharacteristics:nil forService:service];
        }
    }
}

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
    if (error)
    {
        NSLog(@"error Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
        return;
    }
    
    for (CBCharacteristic *characteristic in service.characteristics)
    {
        NSLog(@"service:%@ 的 Characteristic: %@",service.UUID,characteristic.UUID);
    }
    
    //获取Characteristic的值,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
    for (CBCharacteristic *characteristic in service.characteristics){
        {
            [peripheral readValueForCharacteristic:characteristic];
        }
    }
    
//    //搜索Characteristic的Descriptors,读到数据会进入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
//    for (CBCharacteristic *characteristic in service.characteristics){
//        [peripheral discoverDescriptorsForCharacteristic:characteristic];
//    }
}

//获取的charateristic的值
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    //打印出characteristic的UUID和值
    //!注意,value的类型是NSData,具体开发时,会根据外设协议制定的方式去解析数据
    NSLog(@"characteristic uuid:%@  value:%@",characteristic.UUID.UUIDString,characteristic.value);
   
    if ([characteristic.UUID.UUIDString isEqualToString:@"1001"]) {
        
        //Bioland-BGM 05应答包 测试数据
        char byte[11];
        memset(byte, 0, 11);
        byte[0] = 0x5a;
        byte[1] = 0x0b;
        byte[2] = 0x05;
        byte[3] = 0x0e;
        byte[4] = 0x0b;
        byte[5] = 0x08;
        byte[6] = 0x0c;
        byte[7] = 0x12;
        byte[8] = 0xa9;
        byte[9] = 0x00;
        byte[10] = 0x00;
        
        [self writeCharacteristic:peripheral characteristic:characteristic value:[NSData dataWithBytes:byte length:11]];
    }
    
    
    if ([characteristic.UUID.UUIDString isEqualToString:@"1002"]) {
        [self notifyPeripheral:peripheral characteristic:characteristic];
    }
    
//    if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"00001002-0000-1000-8000-00805f9b34fb"]]) {
//        [self notifyPeripheral:peripheral characteristic:characteristic];
//    }
}


////搜索到Characteristic的Descriptors
//-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//    
//    //打印出Characteristic和他的Descriptors
//    NSLog(@"characteristic uuid:%@",characteristic.UUID);
//    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(@"characteristic uuid:%@  value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
//}



//写数据
-(void)writeCharacteristic:(CBPeripheral *)peripheral
            characteristic:(CBCharacteristic *)characteristic
                     value:(NSData *)value{
    
    //打印出 characteristic 的权限,能够看到有不少种,这是一个NS_OPTIONS,就是能够同时用于好几个值,常见的有read,write,notify,indicate,知知道这几个基本就够用了,前连个是读写权限,后两个都是通知,两种不一样的通知方式。
    /*
     typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
     CBCharacteristicPropertyBroadcast												= 0x01,
     CBCharacteristicPropertyRead													= 0x02,
     CBCharacteristicPropertyWriteWithoutResponse									= 0x04,
     CBCharacteristicPropertyWrite													= 0x08,
     CBCharacteristicPropertyNotify													= 0x10,
     CBCharacteristicPropertyIndicate												= 0x20,
     CBCharacteristicPropertyAuthenticatedSignedWrites								= 0x40,
     CBCharacteristicPropertyExtendedProperties										= 0x80,
     CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)	= 0x100,
     CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0)	= 0x200
     };
     */
    
    NSLog(@"write %lu", (unsigned long)characteristic.properties);
    
    //只有 characteristic.properties 有write的权限才能够写
    if(characteristic.properties & CBCharacteristicPropertyWrite){
        /*
         最好一个type参数能够为CBCharacteristicWriteWithResponse或type:CBCharacteristicWriteWithResponse,区别是是否会有反馈
         */
        [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
    }else{
        NSLog(@"该字段不可写!");
    }
}



//设置通知
-(void)notifyPeripheral:(CBPeripheral *)peripheral
             characteristic:(CBCharacteristic *)characteristic{
    NSLog(@"Notify %lu", (unsigned long)characteristic.properties);

    //设置通知,数据通知会进入:didUpdateValueForCharacteristic方法
    [peripheral setNotifyValue:YES forCharacteristic:characteristic];
}

//取消通知
-(void)cancelNotifyPeripheral:(CBPeripheral *)peripheral
                   characteristic:(CBCharacteristic *)characteristic{
    
    [peripheral setNotifyValue:NO forCharacteristic:characteristic];
}

//中止扫描并断开链接
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
                 peripheral:(CBPeripheral *)peripheral{
    //中止扫描
    [centralManager stopScan];
    //断开链接
    [centralManager cancelPeripheralConnection:peripheral];
}

以上代码基于 《CoreBluetooth》 实现蓝牙简单交互,全部操做均在其代理方法中实现,过程十分繁琐,这里推荐一个蓝牙开源库:BabyBluetooth线程

相关文章
相关标签/搜索