在现在这份工作之前做了挺长时间的“智能门禁”“智能家居”方面的Ios开发,都是基于sip协议实现的手机与硬件设备的交互。用到的开源库分别是idoubs(doubango)、linphone,相比较而言linphone的通话效果会好些也更稳当一些。
在软硬件结合的产品中蓝牙开发是必不可少的,因为蓝牙是一种短距离、高效、无需网络的通讯方式。Ios蓝牙分为“中心者模式“和”外设模式“。前者是将手机作为蓝牙信号的接收端,用来连接蓝牙设备并实现手机与外设间的数据传递。后者是将手机作为蓝牙信号的发射端,无需连接蓝牙设备而只需通过发送广播包的形式将数据(加密)广播出去即可,外设中的蓝牙模块在接收到该数据后进行解密->信息比对,进而进行其他操作。
中心者模式
这里需要使用到CoreBluetooth.framework中的CBCentralManager、CBPeripheral两个类,分别用来连接蓝牙和读写蓝牙传递的数据。
蓝牙连接:
1、创建蓝牙管理对象
CBCentralManager *manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue() options:nil];
opetion: 设置系统自带选项
CBCentralManagerOptionShowPowerAlertKey 默认为NO即若手机蓝牙未打开则不弹框提示,可设置为YES CBCentralManagerOptionRestoreIdentifierKey app被杀死,重新恢复manager的ID CBCentralManagerScanOptionAllowDuplicatesKey 默认为NO,过滤功能是否启用,每次寻找都会合并相同的peripheral。如果设为YES的话每次都能接受到来自peripherals的广播包数据。
CBCentralManagerScanOptionSolicitedServiceUUIDsKey 扫描的时候只会扫描到包含这些UUID的设备(数组)。CBConnectPeripheralOptionNotifyOnConnectionKey 默认为NO,APP被挂起时如果已连接到peripheral,是否要给APP一个提示框。
CBConnectPeripheralOptionNotifyOnDisconnectionKey 默认为NO,APP被挂起时,恰好在这个时候断开连接,要不要给APP一个断开提示。
CBConnectPeripheralOptionNotifyOnNotificationKey 默认为NO,APP被挂起时,是否接受到所有的来自peripheral的包都要弹出提示框。
2、检测手机蓝牙状态并扫描蓝牙设备
#pragma mark CBCentralManagerDelegate
//上面创建中心管理者后 这里会自动监测蓝牙的使用状态
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;
根据central.state状态值判断:
CBCentralManagerStateUnknown未知状态/CBCentralManagerStateResetting复位/CBCentralManagerStateUnsupported不支持/CBCentralManagerStateUnauthorized未授权/CBCentralManagerStatePoweredOff未开启/CBCentralManagerStatePoweredOn已开启
在蓝牙开启后执行[manager scanForPeripheralsWithServices:nil options:nil];搜索全部蓝牙 或 [manager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:@"EEFF"]] options:nil];搜索指定UUID的蓝牙。
3、连接蓝牙设备
//发现外设
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{ //接下来在这里根据获取的外设peripheral信息对搜索到的(符合特定服务)蓝牙做筛选。
if([peripheral.name isEqualToString:@"TESTNAME"]) {
[self.centralManager connectPeripheral:peripheral options:nil]; //连接
[self.centralManager stopScan]; }} //停止扫描
4、检测与蓝牙设备的连接状态
//中心管理者连接外设成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
//到这里上面中心管理者的代理方法就执行完了,并且已经连接到了指定的蓝牙设备,所以下面要设置_peripheral的代理并开始读取蓝牙携带的数据。
[peripheral setDelegate:self];
[peripheral discoverServices:nil]; //搜索该蓝牙对应的所有服务
}
//外设连接失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"%s, line = %d, %@=连接失败", __FUNCTION__, __LINE__, peripheral.name);
}
//丢失连接- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"%s, line = %d, %@=断开连接", __FUNCTION__, __LINE__, peripheral.name);
[self.centralManager connectPeripheral:peripheral options:nil]; //重新连接
}
读写蓝牙数据
5、搜索蓝牙特征
//已经发现该蓝牙对应的服务(根据蓝牙设备搜索服务)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error{
for(CBService *service in _peripheral.services) {
[_peripheral discoverCharacteristics:nil forService:service]; //搜索service服务对应的特征
}}
6、根据特征属性做对应的读写操作
//已经搜索到服务对应的特征(根据服务搜索特征,然后判断特征是否可读、可写,最后根据特征的读写属性进行写入和读取操作)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
for(CBCharacteristic *characteristic in service.characteristics) {
if((unsigned long)characteristic.properties == 10) { //支持读写的属性对应的特征才能写入数据
[peripheral writeValue:[_inputStr dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse]; //将数据写入指定的特征(即给蓝牙设备发送数据)
}}}
7、检测是否读写成功
//用于检测中心向外设写数据是否成功
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"写入蓝牙特征数据失败 = %@",error.userInfo); }
else{
NSLog(@"写入蓝牙特征数据成功"); }
[peripheral readValueForCharacteristic:characteristic];}
8、订阅特征并实时更新数据
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;
外设模式
这里需要用到CoreBluetooth.framework中的CBPeripheralManager类,把手机作为蓝牙广播包的发送者。
1、创建设备类对象并检测手机蓝牙状态
CBPeripheralManager *manager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue() options:nil];
#pragma mark CBPeripheralMangerDelegate
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral;
2、发广播包
[manager startAdvertising:@{CBAdvertisementDataLocalNameKey : @"将显示的蓝牙名称", CBAdvertisementDataServiceUUIDsKey:@"自定义的UUID"}];
注:startAdvertising这个方法只允许设置CBAdvertisementDataLocalNameKey(本地名称)、CBAdvertisementDataServiceUUIDsKey(UUID)两个参数,其他键值key可在“中心模式”下的设备中查看使用。 CBAdvertisementDataIsConnectable(外设的可连接数)这个参数是广播包自带的默认参数(1->可连接 0->不可连接)。广播包中能传输的数据最多为32个字节。
3、检测手机广播是否成功
//手机已经开始广播广播
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(nullable NSError *)error;
下面简要说明下我们产品的蓝牙应用场景
“中心者模式”:
步骤:开启蓝牙->搜索固定标示的蓝牙设备->连接蓝牙->向蓝牙模块写入加密指令->蓝牙模块将指令信息输出到硬件主板->执行具体操作
注:为了用户有更好的使用体验,这里的连接蓝牙是自动连接即连接时无需授权(蓝牙模块开发人员处理),且为了保证蓝牙连接的稳定性我们使用的蓝牙最大连接数为一个。
“外设模式”:
步骤:开启蓝牙->将带有固定标示的加密数据写入广播包的CBAdvertisementDataLocalNameKey中->硬件中的蓝牙模块识别到该数据包并将相关数据输出到主板->解密数据并执行相应操作
注:这个方式需要和蓝牙模块开发人员、硬件开发人员共同制定数据传输协议