利用iOS原生框架实现蓝牙4.0通讯功能

利用iOS原生框架实现蓝牙4.0通讯功能

蓝牙4.0是蓝牙3.0+HS规范的补充,专门面向对成本和功耗都有较高要求的无线方案,可广泛用于卫生保健、体育健身、家庭娱乐、安全保障等诸多领域。
它支持两种部署方式:双模式和单模式。双模式中,低功耗蓝牙功能集成在现有的经典蓝牙控制器中,或再在现有经典蓝牙技术(2.1+EDR/3.0+HS)芯片上增加低功耗堆栈,整体架构基本不变,因此成本增加有限。百度百科

蓝牙的开发过程本质上是非常简单的,困难的地方在于对蓝牙传递过来的数据解析。为此,楼主将会从以下几个方面对实现iOS原生框架实现蓝牙4.0通讯功能:

  • iOS BLE4.0原生框架
  • 建立蓝牙通讯所必要的条件
  • 开发过程中需要注意的点
  • 自定义BLE链接框架和代码简介
  • 测试demo

通过上面几个步骤,相信会对有需要的开发人员提供一定的思路和帮助。


一、iOS BLE4.0原生框架CoreBluetooth

CoreBluetooth框架的介绍很多,因此为了节省篇幅,我会对用到的方法做一个快速的解释。

需要注意的是,本文的蓝牙连接方式为:中心者模式
简单的说中心者模式就是手机设备连接其他外设(大多的蓝牙开发都是这种情况)

CoreBluetooth连接一个外设一般分为两个大的阶段

  • 查找并连接指定设备阶段
  • 连接设备指定服务传输数据阶段

这两个阶段只有在进行到服务阶段后才可以正常连接数据。接下来对此进行简单的介绍

1、查找连接设备阶段CentralManager Delegate (设备级别)

该阶段执行的顺序如下:

  • 创建蓝牙中心者模式对象
//该方法用于创建一个蓝牙中心者模式对象,需要传递一个代理对象和一个分线程(数据一般都是在分线程采集的)
- (instancetype)initWithDelegate:(nullableid<CBCentralManagerDelegate>)delegate queue:(nullable dispatch_queue_t)queue; 
  • 启动蓝牙中心者模式
//传入一个蓝牙中心者对象,启动蓝牙功能
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;

在这个代理方法中,会实现检查设备蓝牙状态:

- (void)centralManagerDidUpdateState:(nonnull CBCentralManager *)central {
    switch (central.state) {
        case CBCentralManagerStatePoweredOn:{
            //开启可用状态
            //在开启状态下启动蓝牙扫描周边设备功能
        }
            break;
        default: {
            //异常状态
        }
            break;
    }
}

楼主在做的时候一般手动调用这个方法触发后续的代码,如果有误还望指出

  • 扫描周边外设
//调用此方法可以搜索周边的蓝牙设备,如果传入制定的UUID可以制定搜索,如果传入nil则会搜索全部
- (void)scanForPeripheralsWithServices:(nullable NSArray<CBUUID *> *)serviceUUIDs options:(nullable NSDictionary<NSString *, id> *)options;
  • 连接外设
//此方法需要传入一个要连接的CBPeripheral对象,传入后会制定连接这个对象
- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options;
  • 连接状态变化的回调
//连接外设成功后,会自动调用此方法
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
//连接外设失败后,会调用此方法
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;
//已连接的外设断开后,会调用此方法
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error;

这三个函数都是针对连接状态发生改变时会调用的方法,尤其是第三个方法是经常用到的。

到此为止,蓝牙连接外设的代理方法就全部介绍完毕了。

2、连接设备指定服务传输数据阶段

该阶段的执行顺序如下:
- 获取设备服务回调

//当连接设备后将会进入下面的方法,在这个方法中我们可获取设备所有的服务,并对我们需要的服务进行过滤,并连接指定的服务
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error;
  • 获取服务特征值回调
//当链接到指定服务并获取到特征值列表后,就会回调下面的函数。
//在这个方法里,我们可以对设备服务提供的特征值进行过滤,并需要保存对应的特征值和服务,便于后期区分和读写。
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error;

确定唯一的服务于特征值后,需要调用下面的三个方法,之后与制定的服务和特征值简历稳定的连接(很重要)。

[peripheral readValueForCharacteristic:characteristic];
[peripheral discoverDescriptorsForCharacteristic:characteristic];
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
  • 读取数据&写入数据
//当建立连接的服务于特征值有数据变化时,将会回调该函数,所有的数据信息都会汇总到这里
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error

所有获得的数据可以从characteristic.value获取

//该方法由CBPeripheral对象调用,可以向CBPeripheral的某一个CBCharacteristic中写入制定的内容,类型为NSData。
//CBCharacteristicWriteType是写入类型:CBCharacteristicWriteWithResponseCBCharacteristicWriteWithoutResponse,可以根据实际情况选择
- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;

到此为止,CoreBluetooth的框架解释已经完成,如果需要了解更相信的内容可以自行搜索。


二、建立蓝牙通讯所必要的条件

该部分提出了建立BLE4.0通讯的必要条件,可以分为以下几个部分:

1、一个支持BLE4.0的手机和外接设备(废话)
2、明确BLE通讯模式(本文只适合中心者模式
3、明确外设所开放的服务(CBService.UUID)和特征值(CBCharacteristic.UUID)
4、 明确与外设约定的通讯协议内容和格式
5、 外设连接的唯一标识符

两个UUID是必须获取的,需以此作为连接的根据
外设连接的唯一标识符也是必须确定的,以此作为区分外接设备。


三、开发过程中需要注意的点

1、建议将功能集合起来作为一个单例类使用
2、建立蓝牙连接必须存储对应的CBPeripheral和CBCharacteristic,否则将无法获取对应的数据
3、发送和接受的数据一般是NSData类型,在Demo中提供了一些实用的解析方法
4、确定好唯一的特征之后一定要添加三个启动方法,否则不会启动数据传输


四、自定义BLE链接框架和代码简介

在工作过程中,由于需要实用蓝牙进行数据传输,因此编写了一个比较实用的BLE4.0蓝牙传输框架。

框架利用block方式理论上可以应用在各种混编代码中(已经通过C++和OC混编调试)

框架的整体设计如下:
这里写图片描述
接下来对各个类的方法进行一个简单介绍:

1、BLELinkManager

BLELinkManager负责与外设的链接,提供了以下几个接口:

  • 创建BLE蓝牙linke控制者
/**
 创建BLELinkManager的单例对象

 @return 返回BLELinkManager对象
 */
+ (BLELinkManager *)sharedManager;
  • 开始连接二维码设备
/**
 开始连接设备
 */
- (void)startLinkDevice;
  • 向蓝牙模块写入数据
/**
 向蓝牙模块写入数据

 @param type 命令字符
 @param number 通道号
 @param speedValue 上传速率,在BLE情况下上限为1000Byte/s
 */
- (void)writeCommandToBLEDeviceWithType:(BLEObjectCommandState)type
                      withChannelNumber:(NSInteger)number
                         withSpeedValue:(int)speedValue;

这里提供了必要的三个接口,具体的内容可以根据实际情况修改。创建、启动、写入、返回这是必须要考虑的。
返回的接口采用Block的方式回调,定义和使用如下

  • 回调Block
/**
 命令信息返回block

 @param channelNumber 通道号
 @param backString 返回的字符串信息
 */
typedef void(^CommandInfoBlock)(NSInteger channelNumber, NSString * backString);

/**
 linke状态信息返回block

 @param linkState 状态码
 @param channelNumber 通道号,当linkState为BLE_DEVICE_OPEN无意义
 @param flag 标签
 */
typedef void(^LinkInfoBlock)(BLEStateEnum linkState,NSInteger channelNumber, bool flag);

//回调Block属性声明//
//必须实现的Block
@property (nonatomic,copy) CommandInfoBlock infoBlock;
//必须实现的Block
@property (nonatomic,copy) LinkInfoBlock linkInfoBlock;

使用时,在需要的位置初始化,直接调用对应的属性即可。

- (void)viewDidLoad {
    [super viewDidLoad];

    linkManager_ = [BLELinkManager sharedManager];

    linkManager_.linkInfoBlock = ^(BLEStateEnum linkState, NSInteger channelNumber, bool flag) {
       //do something
    };

    linkManager_.infoBlock = ^(NSInteger channelNumber, NSString *backString) {
       // do something
    };
}

这里仅仅是介绍了使用方法,具体的代码可以参考提供的Demo。
需要注意的是,调用方法startLinkDevice()需要保证连接设备前已经获取到要连接设备的列表或者地址。
获取的方法多种多样,Demo采用的是二维码扫设备Mac地址的方式,仅供参考。

2、BLEChannelManager

BLEChannelManager控制通道数目和通道信息,表示现在链接了几个对象。

设计中考虑到断开连接后再连接的情况,对链接的设备次序进行了记录和保存。
Demo中上限为4台设备,存储的顺序为0,1,2,3。当出现空闲通道的时候会优先存储空闲通道。
例如:当0,1,2,3都连接成功,先把1,3断开,再接入设备时会按照1,3的顺序存储。

  • 创建BLEChannelManager单例对象(不多过多解释)
  • 添加连接设备的方法
/**
 添加蓝牙通道设备

 @param deviceQrCoder 设备唯一标志,此处考虑为二维码
 @param discoveredPeripheral 设备信息
 @return 是否添加成功
 */
- (BOOL)addBleChannelObjectByDeviceQrCoder:(NSString *)deviceQrCoder
                            withPeripheral:(CBPeripheral *)discoveredPeripheral;
/**
 根据BLE通道对象添加设备

 @param objects 蓝牙通道对象
 @return 是否添加成功
 */
- (BOOL)addBleChannelObjectToArray:(BLEChannelObject *)objects;

这里的添加的设备为设备级别的,还没有进入到服务级别。

  • 设置服务状态
/**
 对某个设备添加可用的服务状态

 @param discoveredPeripheral 设备状态
 @param characteristic 特征值
 */
- (void)setCharacteristicByPeripheral:(CBPeripheral *)discoveredPeripheral
                   withCharacteristic:(CBCharacteristic *)characteristic;
  • 清除设备的方法
/**
 根据通道值删除重设某个通道信息

 @param indexes 通道值
 */
- (void)removeBleChannelObjectByNumber:(NSInteger)indexes;
/**
 清除全通道
 */
- (void)cleanChannelObject;
  • 获取和查询等方法
 /**
 根据通道值获取到蓝牙通道对象

 @param indexes 通道值
 @return 蓝牙通道对象
 */
- (BLEChannelObject *)getBleChannelObjectByNumber:(NSInteger)indexes;


/**
 根据CBPeripheral获取蓝牙通道值

 @param peripheral CBPeripheral对象
 @return 蓝牙通导值
 */
- (NSInteger)getBleChannelIndexByPeripheral:(CBPeripheral *)peripheral;


/**
 获取所有的蓝牙通道信息

 @return 蓝牙通道对象数组
 */
- (NSMutableArray *)getBleChannelObjectArray;

/**
 是否可以添加新的设备

 @return 是否可以
 */
- (BOOL)canAddNewBLEDevic;

上面提供的方法基本足够正常的使用,也可以根据自己的情况添加

3、BLEMessageManager

BLEMessageManager的功能非常简单,但是此处需要与硬件进行沟通设定,本处作为一个简单的例子

typedef NS_ENUM(NSInteger, BLEObjectCommandState){

    //ABA命令,主要用于......
    BLEOBJECTCOMMAND_ABA = 0,    //0

    //BAB命令,主要用于......
    BLEOBJECTCOMMAND_BAB = 1,    //1

    //CDC,主要用于......
    BLEOBJECTCOMMAND_CDC = 2,    //2

    //DCD,主要用于......
    BLEOBJECTCOMMAND_DCD = 3,    //3

};

提供的接口内容也非常简单,详情参考Demo不做过多解释,不过在.m文件中,提供了2种将需要的命令转成可写入的命令的方法:

  • 字符串转成对应的数据流
/字符串转byteData
-(NSMutableData *)dataFromString:(NSString *)string
{
    NSMutableData *data_ = [NSMutableData new];
    unsigned char whole_byte_;
    char byte_chars[3] = {'\0','\0','\0'};

    int i_ = 0;
    int length_ = (int)string.length;

    while (i_ < length_-1) {
        char c_ = [string characterAtIndex:i_++];

        if (c_ < '0' || (c_ > '9' && c_ < 'a') || c_ > 'f')
            continue;
        byte_chars[0] = c_;
        byte_chars[1] = [string characterAtIndex:i_++];
        whole_byte_ = strtol(byte_chars, NULL, 16);
        NSLog(@"---whole_byte_--->%c",whole_byte_);
        [data_ appendBytes:&whole_byte_ length:1];

    }
    return data_;
}

这个方法可以将字符串转成外设可用的NSData数据流,有一定的通用性

NSMutableData *data_ = [NSMutableData new];
dig_4=0; dig_3=2; dig_2=0; dig_1=0;
unsigned int whole_byte_[10];
whole_byte_[0]=0xAA;
whole_byte_[1]=0x03;
whole_byte_[2]=0x12;
whole_byte_[3]=0x72;
whole_byte_[4]=(dig_4*10+dig_3);//千、百位
whole_byte_[5]=(dig_2*10+dig_1);//十、个位
whole_byte_[6]=0x01;
whole_byte_[7]=0x72;
whole_byte_[8]=0x30;
whole_byte_[9]=0x55;
int i = 0;
while (i<10) {                
    [data_ appendBytes:&whole_byte_[i] length:1];
    i++;
}

这种方式其实与第一种没什么太多区别,仅仅是直接将需要的数据转成
一个定长的unsigned int的C数组,建议用第一种方式。

五、演示Demo

具体的代码信息太多,已经将Demo上传到Coding,如果有需要的可以戳下方链接下载查看,有问题也欢迎提出来一起修改进步~
下载地址:
https://git.coding.net/SupDongLei/BLELinkManager.git

插嘴一句:BLELinkManager使用Block作为属性,然后调用的方法,可以应对绝大多数的混编情况,在于C++混编情况下表现良好,不失为一种处理混编的方式。
采用这种方式已经实现了包括:混编读写文件,URL Scheme,蓝牙链接,原生扫码等功能(但是需要注意Block截取对象和运行顺序)

ps:蓝牙其实明白了原理…也没那么难的。


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值