iOS蓝牙开发详解
随着物联网技术的高速发展,蓝牙开发也越来越火热。不论是智能穿戴设备还是蓝牙家具,车联网蓝牙,都是通过蓝牙协议来进行通信。废话不多说了,先向大家简单的介绍有关蓝牙开发的知识。蓝牙低能耗(BLE),以下介绍的都是围绕iOS的蓝牙(4.0)框架展开的。
蓝牙开发分为中心管理者者模式和外设管理者模式:1.常用的(90%)就是使用中心管理者者模式作为开发,就是我们手机作为中心管理者,扫描、连接蓝牙外设;2.外设管理者模式,这个基本用到的比较少,我们手机自己作为外设,自己创建服务和特征,然后通过广播,把自己的信息广播出去,其他的设备可以扫描连接到我们的手机。
在做蓝牙开发之前,最好先了解一些概念:
广播包: (Advertising)广播包是一个公开的信息,别人不需要连接你的设备就能读取到的信息;(iOS端由于苹果的限制,无法获取设备的Mac地址,只能让硬件放于广播包中。)
外设: (peripheral)一个蓝牙硬件就是一个外设,一个外设下有一个或者多个服务;
- 服务: (services)蓝牙外设对外广播的必定会有一个服务,可能也有多个,服务下面包含着一些特征值,服务可以理解成一个端口;
- 特征: (characteristic)特征可以理解成一个HTTP的接口,一般特征都会有value,也就是特征值。每个特征都有一个或多个权限,例如读、写、通知。向一个特征写值可以看做是发起一个HTTP请求。(和HTTP请求不同的是,如果你写入的type和硬件底层的type不配套会写不进去。无论是哪个type,系统都会通过代理回调告知我们是否写入。)因此特征是与外界交互的最小单位;
- UUID:可以理解成蓝牙上的唯一标识符,为了区分不同的服务和特征,我们就用UUID来代表服务和特征。
蓝牙连接可以大致分为以下几个步骤:
- 建立一个Central Manager实例,实现Central代理协议进行蓝牙管理。
- 打开蓝牙,扫描周围外设,获取广播包数据信息。
- 根据广播包数据连接指定外设。(目前我测试一个APP连接7个外设是没有问题,但是一个手机能连接多少个并没有测试,如果有知道的请告诉我。)
- 获得外设的服务。
- 获得服务的特征。
- 从外设读数据。
- 给外设发送数据。
#pragma mark - 扫描、连接操作
/**
开始搜索
*/
- (void)startScan {
[self.manager scanForPeripheralsWithServices:nil options:nil];
}
/**
开始搜索
*/
- (void)startScanWith:(didDiscoverPeripheral)block {
self.didDiscoverPeripheralBlock = block;
[self.manager scanForPeripheralsWithServices:nil options:nil];//扫描设备,可设置扫描的服务和选项。不填写可以加快扫描速度。
}
/**
连接指定设备,可设置连接选项
*/
- (void)linkWith:(Device *)device options:(nullable NSDictionary<NSString *,id> *)dic handle:(didConnectPeripheral)block {
//防止多次点击,连接多次,只让连接一个,如果做BLE4.0多连接可以取消这个限定
if (self.linkStatus != BLEManagerUnLinked) {
return;
}
self.linkStatus = BLEManagerLinking;
self.didConnectPeripheralBlock = block;
//iOS端链接外设只能用扫描到的外设,如果连接的外设为空会crash。
if (device.per) {
[self.manager connectPeripheral:device.per options:dic];
}
[self performSelector:@selector(linkOuttime) withObject:nil afterDelay:5];//超过五秒则认为超时,取消连接
self.linkDevice = device;
}
/**
连接超时
*/
- (void)linkOuttime {
if (self.linkStatus != BLEManagerLinked) {
[self disconnectPeripheral:self.manager peripheral:self.linkDevice.per];
NSError *error = [NSError errorWithDomain:@"连接超时" code:1 userInfo:nil];
self.linkStatus = BLEManagerUnLinked;
self.didConnectPeripheralBlock(NO, error);
}
}
//断开连接
- (void)disconnectPeripheral:(CBPeripheral *)peripheral {
//断开连接 如果外设为空会crash
if (peripheral) {
[self.manager cancelPeripheralConnection:peripheral];
} else {
if (self.didDisconnectPeripheralBlock) {
self.didDisconnectPeripheralBlock(peripheral, nil);
}
}
[self cleanData];
}
- (void)cleanData {
//数据回归初始化状态
self.datas = [NSMutableArray array];
self.allDevice = [@{} mutableCopy];
self.linkStatus = BLEManagerUnLinked;
}
#pragma mark - 发送数据
/**
发送数据
@param data 数据
@param type 写入类型
*/
- (void)sendData:(NSData *)data withType:(CBCharacteristicWriteType)type{
//数据发送,发送的时候,外设特征值必须存在,发送的数据是NSData类型
/*
具体使用哪种类型,询问硬件工程师或者查看接口文档
两者的区别在于写入的通道不同(硬件工程师和我说的),如果type不对应则会写入失败。
type:
type为CBCharacteristicWriteWithResponse类型时,didWriteValueForCharacteristic会返回写入调用的结果,而CBCharacteristicWriteWithoutResponse则不会。。
*/
[self.send sendData:data withType:type];
}
/**
外设的特征值更新通知
@param peripheral 连接(更新)的外设
@param characteristic 连接(更新)的外设的特征值
@param error 错误信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
//打印出characteristic的UUID和值
NSLog(@"characteristic.uuid = %@, value = %@",characteristic.UUID,characteristic.value);
//!注意,value的类型是NSData,具体开发时,会根据外设协议制定的方式去解析数据
if ([characteristic.UUID.UUIDString isEqualToString:@"readCharacteristic"]) {
NSData *data = characteristic.value;
//把读取到的数据回调出去
for (getData block in self.getDataBlockArr) {
block(data);
}
} else if ([characteristic.UUID.UUIDString isEqual:readCharacteristicUUID]||[characteristic.UUID.UUIDString isEqual:notiyCharacteristicUUID]||[characteristic.UUID.UUIDString isEqual:writeCharacteristicUUID]) {
NSData *data = characteristic.value;
//把读取到的数据回调出去
for (getData block in self.getDataBlockArr) {
block(data);
}
}
}
/**
已经写完数据回调
@param peripheral 连接(写入)的外设
@param characteristic 连接(写入)的外设的特征值
@param error 是否写入成功,如果error为nil则写入成功,否则error会存在
*/
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error {
NSLog(@"didWriteValue error = %@", error);
}