官方文档:
Core Bluetooth
Core Bluetooth的封装
YmsCoreBluetooth
Device Firmware Update (OTA-DFU)
iOS DFU Library
IOS-nRF-Toolbox
IOS-nRF-Toolbox
工具APP:
LightBlue、nRF Connect、蓝牙助手等。
iOS 蓝牙
iOS中提供了4个框架用于实现蓝牙连接。
- GameKit.framework(用法简单)
只能用于iOS设备之间的同个应用内连接,多用于游戏(eg:棋牌类),从iOS7开始过期 - MultipeerConnectivity.framework(代替1)
只能用于iOS设备之间的连接,从iOS7开始引入,主要用于非联网状态下,通过wifi或者蓝牙进行文件共享(仅限于沙盒的文件),多用于附近无网聊天 - ExternalAccessory.framework(MFi)
可用于第三方蓝牙设备交互,但是蓝牙设备必须经过苹果MFi认证(国内很少) - CoreBluetooth.framework(常用 Apple推行蓝牙的核心)
可用于第三方蓝牙设备交互,必须要支持蓝牙4.0
硬件至少是4s,系统至少是iOS6 (现iPhoneX 蓝牙5.0)
蓝牙4.0以低功耗著称,一般也叫BLE(Bluetooth Low Energy)
目前应用比较多的案例:运动手环,嵌入式设备,智能家居
buletouch low energy,蓝牙 4.0 设备因为低耗电,所以也叫做 BLE,CoreBluetooth框架就是苹果公司为我们提供的一个库,我们可以使用这个库和其他支持蓝牙4.0的设备进行数据交互。
工作模式:蓝牙通信中,首先需要提到的就是 central 和 peripheral 两个概念。这是设备在通信过程中扮演的两种角色。直译过来就是 [中心] 和 [周边(可以理解为外设)]。iOS 设备既可以作为 central,也可以作为 peripheral,这主要取决于通信需求。
权限配置
在info.plist中配置以下权限:
NSBluetoothPeripheralUsageDescription
App需要您的同意,才能访问蓝牙
NSBluetoothAlwaysUsageDescription
App需要您的同意,才能访问蓝牙
目录:project->Targets->Capabilities→Background Modes(手动打开)→Uses Bluetooth LE Accessories(选中)
使用CoreBluetooth框架做蓝牙连接传输
在iPhone上使用蓝牙有两种模式 可以一对一 也可以一对多(需服务通道不同)
现一般主要使用于第三方/自家蓝牙硬件智能产品(如:心率计设备、蓝牙音箱、蓝牙手环、蓝牙车锁等等)
蓝牙传输上限是20字节 所以大数据传输会涉及到拆包、拼包、校验等
蓝牙连接流程
- 建立中心设备管理者
- 扫描外设
- 连接外设
- 扫描外设中的服务
- 扫描外设中的特征
- 订阅或读取特征值
- 获取外设中的数据或将数据写入外设
中心模式 CenterManager (一般Phone作为中心设备)
手机可以作为中心设备也可以作为外设来使用 CenterManage主要处理对蓝牙状态的控制和对外围设备的状态处理
建立中心角色
- 监听蓝牙状态
- 扫描外设(Discover Peripheral) 接收外设蓝牙广播 (可以扫描包含制定服务的设备)
- 连接外设(Connect Peripheral)
- 断开连接(Disconnect)
外设模式
CenterManager 扫描链接外设成功后 启动一个Peripheral外设管理对象 负责外设数据的操作处理
- 启动一个Peripheral外设管理对象 负责外设数据的操作处理
- 扫描外设中的服务和特征(Discover Services And Characteristics)
- 获取外设的services (基本服务(电池信息和设备信息)、硬件自定服务)
- Discover指定Service下的特征 获取外设的Characteristics,
- 通过指定特征( Characteristics)订阅(Notify)/读取(Read)/写入(Write) 等操作
- 获取Characteristics的Descriptor和Descriptor的值
- 根据业务做处理
- Characteristics作为蓝牙数据传输操作的做小单元
Characteristics作为蓝牙数据传输操作的做小单元
什么是服务和特征(service and characteristic)
每个设备都会有1个或者多个服务
每个服务里都会有1个或者多个特征
特征就是具体键值对,提供数据的地方
每个特征属性分为:订阅(Notify)/读取(Read)/写入(Write) 等等
中心角色的实现:(central)
(1)、初始化中央管理器对象
/**
第一个参数:代理
第二个参数:队列(nil为不指定队列,默认为主队列)
第三个参数:实现状态保存的时候需要用到 eg:@{CBCentralManagerOptionRestoreIdentifierKey:@"centralManagerIdentifier"}
*/
centerManager = [[CBCentralManager alloc]initWithDelegate:self queue:queue options:options];
中央管理器会调用 centralManagerDidUpdateState:通知蓝牙的状态
(2)、发现外围设备
//主动发起扫描外设 开始扫描符合服务serviceUUIDs的外设
[centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:SERVICE_UUID]] options:nil];
//主动停止扫描
- (void)stopScan;
每次中央管理器发现外围设备时,它都会调用centralManager:didDiscoverPeripheral:advertisementData:RSSI:其委托对象的方法。
(3)、发现想要的外围设备进行连接
#pragma mark -- 扫描发现到任何一台设备都会通过这个代理方法回调
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
//过滤掉无效的结果
if (peripheral == nil||peripheral.identifier == nil/*||peripheral.name == nil*/)
{
return;
}
NSString *pername =[NSString stringWithFormat:@"%@",peripheral.name];
NSLog(@"所有服务****:%@",peripheral.services);
NSLog(@"蓝牙名字:%@ 信号强弱:%@",pername,RSSI);
//连接需要的外围设备
[self connectPeripheral:peripheral];
//将搜索到的设备添加到列表中
[self.peripherals addObject:peripheral];
if (_didDiscoverPeripheralBlock) {
_didDiscoverPeripheralBlock(central,peripheral,advertisementData,RSSI);
}
}
如果连接请求成功,则中央管理器调用centralManager:didConnectPeripheral:其委托对象的方法。
(4)、发现所连接的外围设备的服务
#pragma mark -- 连接成功、获取当前设备的服务和特征 并停止扫描
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"%@",peripheral);
// 设置设备代理
[peripheral setDelegate:self];
// 根据服务UUID发现服务
[peripheral discoverServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]];
NSLog(@"Peripheral Connected");
if (_centerManager.isScanning) {
[_centerManager stopScan];
}
NSLog(@"Scanning stopped");
}
发现指定的服务时,外围设备(CBPeripheral你连接的对象)会调用peripheral:didDiscoverServices:其委托对象的方法。
(5)、发现服务的特征
#pragma mark -- 获取当前设备服务services
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if (error) {
NSLog(@"Error discovering services: %@", [error localizedDescription]);
return;
}
NSLog(@"所有的servicesUUID%@",peripheral.services);
//遍历所有service
for (CBService *service in peripheral.services)
{
NSLog(@"服务%@",service.UUID);
//找到你需要的servicesuuid
if ([[NSString stringWithFormat:@"%@",service.UUID] isEqualToString:SERVICE_UUID])
{
// 根据UUID寻找服务中的特征
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:CHARACTERISTIC_UUID]] forService:service];
}
}
}
peripheral:didDiscoverCharacteristicsForService:error:当发现指定服务的特征时,外围设备调用其委托对象的方法。
(6)、检索特征价值
- 读取特征的值 (该特征有Read属性)
[peripheral readValueForCharacteristic:interestingCharacteristic];
注意: 并非所有特征都是可读的。你可以通过检查其properties属性是否包含CBCharacteristicPropertyRead常量来确定特征是否可读。如果尝试读取不可读的特征值,则peripheral:didUpdateValueForCharacteristic:error:委托方法将返回合适的错误。
- 订阅特征的值(该特征有Notify属性)
虽然使用该readValueForCharacteristic:方法读取特征值对静态值有效,但它不是检索动态值的最有效方法。检索随时间变化的特征值 - 例如,你的心率 - 通过订阅它们。订阅特征值时,您会在值更改时收到外围设备的通知。
[peripheral setNotifyValue:YES forCharacteristic:interestingCharacteristic];
- 写一个特征的值 (该特征有Write属性)
有时写一个特征的值是有意义的。例如,如果你的应用程序与蓝牙低功耗数字恒温器交互,你可能需要为恒温器提供设置房间温度的值。如果特征值是可写的,则可以NSData通过调用外设writeValue:forCharacteristic:type:方法将数据值;
[self.discoveredPeripheral writeValue:data forCharacteristic:self.characteristic1 type:CBCharacteristicWriteWithResponse];
写入特征的值时,指定要执行的写入类型。在上面的示例中,写入类型CBCharacteristicWriteWithResponse指示外围设备通过调用peripheral:didWriteValueForCharacteristic:error:其委托对象的方法让您的应用程序知道写入是否成功。
CBPeripheral外设的状态和数据传输处理
//外设连接状态state
typedef NS_ENUM(NSInteger, CBPeripheralState) {
CBPeripheralStateDisconnected = 0,//断开
CBPeripheralStateConnecting,//正在连接
CBPeripheralStateConnected,//已连接
CBPeripheralStateDisconnecting NS_AVAILABLE(10_13, 9_0),//正在断开连接
} NS_AVAILABLE(10_9, 7_0);
//设备写入服务类型 一般情况下都是CBCharacteristicWriteWithResponse
typedef NS_ENUM(NSInteger, CBCharacteristicWriteType) {
CBCharacteristicWriteWithResponse = 0,//有响应
CBCharacteristicWriteWithoutResponse,//无响应
};
//CBPeripheral继承自CBPeer,独一的标示该设备的id
@property(readonly, nonatomic) NSUUID *identifier NS_AVAILABLE(10_13, 7_0);
//用来接收设备事件的代理
@property(weak, nonatomic, nullable) id delegate;
//只读属性 设备名称
@property(retain, readonly, nullable) NSString *name;
//外设连接状态CBPeripheralState类型
@property(readonly) CBPeripheralState state;
//数组,内含扫描到的设备的服务
@property(retain, readonly, nullable) NSArray *services;
/*
* 1、如果值为YES,远程设备有空间发送一个没有响应的写服务.
* 2、如果值为 NO,如果值被设置为YES时,当前写服务被冲刷,方法peripheralIsReadyToSendWriteWithoutResponse:将会被调用
*/
@property(readonly) BOOL canSendWriteWithoutResponse;
/*!
* @method readRSSI
*
* @discussion 当连接成功,检索当前连接的信号强度。回调方法为 peripheral:didReadRSSI:error:
*
* @see peripheral:didReadRSSI:error:
*/
- (void)readRSSI;
/*!
* @method discoverServices:
*
* @param serviceUUIDs 需要扫描的设备的服务的id,如果为nil,则扫描所有的服务
*
* @discussion 扫描发现设备所有可用的服务
*
* @see :扫描回调方法peripheral:didDiscoverServices:
*/
- (void)discoverServices:(nullable NSArray *)serviceUUIDs;
/*!
* @method discoverIncludedServices:forService:
*
* @param includedServiceUUIDs 需要发现的服务service中的服务id列表,如果为nil,则扫描服务内所有的服务,这样的话会比较慢,不推荐
* @param service 服务
*
* @discussion 发现指定服务service内的服务
*
* @see 回调方法: peripheral:didDiscoverIncludedServicesForService:error:
*/
- (void)discoverIncludedServices:(nullable NSArray *)includedServiceUUIDs forService:(CBService *)service;
/*!
* @method discoverCharacteristics:forService:
*
* @param characteristicUUIDs 数组,内含需要被发现的所有特征值类型,如果为nil,则为所有特征值。
* @param service 服务
*
* @discussion 发现指令服务的服务特征值
*
* @see 回调方法为: peripheral:didDiscoverCharacteristicsForService:error:
*/
- (void)discoverCharacteristics:(nullable NSArray *)characteristicUUIDs forService:(CBService *)service;
/*!
* @method readValueForCharacteristic:
*
* @param characteristic 需要读取的服务特征值
*
* @discussion 读取服务特征值的值,调用该方法则读取该方法前最新的蓝牙系统缓存的从外设读取的数据
*
* @see 回调方法: peripheral:didUpdateValueForCharacteristic:error:
*/
- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic;
/*!
* @method maximumWriteValueLengthForType:
*
* @discussion 获取向一个写服务可发送的最大字节数
*
* @see 该写服务可通过调用writeValue:forCharacteristic:type:写数据
*/
- (NSUInteger)maximumWriteValueLengthForType:(CBCharacteristicWriteType)type NS_AVAILABLE(10_12, 9_0);
/*!
* @method writeValue:forCharacteristic:type:
*
* @param data 待写数据
* @param characteristic 写服务特征
* @param type 写服务的类型(有/无响应)
*
* @discussion 向指定服务写数据,
* 1、如果指定CBCharacteristicWriteWithResponse类型,写入结果将会回调 peripheral:didWriteValueForCharacteristic:error:方法
* 2、如果指定为CBCharacteristicWriteWithoutResponse类型,同时canSendWriteWithoutResponse为NO时,则数据将尽最大努力,但不会被保证成功。
*
*/
- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type;
Delegate
/*!
* @method peripheralDidUpdateName:
*
* @param peripheral 需要更新名称的设备
*
* @discussion 该方法被触发当设备的名称改变
*/
- (void)peripheralDidUpdateName:(CBPeripheral *)peripheral NS_AVAILABLE(10_9, 6_0);
/*!
* @method peripheral:didModifyServices:
*
* @param peripheral 需要更新的设备
* @param invalidatedServices The services that have been invalidated
*
* @discussion 该方法触发当设备的服务改变时候
* 服务可以被重新发现通过discoverServices: 方法
*/
- (void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray *)invalidatedServices NS_AVAILABLE(10_9, 7_0);
/*!
* @method peripheralDidUpdateRSSI:error:
*
* @param peripheral 需要更新的设备.
* @param error 返回错误原因.
*
* @discussion 该方法是readRSSI: 的回调
*
* @deprecated 使 {@link peripheral:didReadRSSI:error:}代替了
*/
- (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(nullable NSError *)error NS_DEPRECATED(10_7, 10_13, 5_0, 8_0);
/*!
* @method peripheral:didReadRSSI:error:
*
* @param peripheral 需要更新的设备
* @param RSSI 设备的RSSI.
*
* @discussion 该方法是readRSSI: 的回调
*/
- (void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(nullable NSError *)error NS_AVAILABLE(10_13, 8_0);
/*!
* @method peripheral:didDiscoverServices:
*
* @param peripheral 当前设备.
* @param error 错误原因
*
* @discussion 该发放为 discoverServices:回调
*
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;
/*!
* @method peripheral:didDiscoverIncludedServicesForService:error:
*
* @param peripheral 当前设备
* @param service 设备服务
* @param error 错误原因.
*
* @discussion 该方法为 discoverIncludedServices:forService: 的回调
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(nullable NSError *)error;
/*!
* @method peripheral:didDiscoverCharacteristicsForService:error:
*
* @param peripheral The peripheral providing this information.
* @param service The CBService
object containing the characteristic(s).
* @param error If an error occurred, the cause of the failure.
*
* @discussion This method returns the result of a @link discoverCharacteristics:forService: @/link call. If the characteristic(s) were read successfully,
* they can be retrieved via service's characteristics
property.
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error;
/*!
* @method peripheral:didUpdateValueForCharacteristic:error:
*
* @param peripheral The peripheral providing this information.
* @param characteristic A CBCharacteristic
object.
* @param error If an error occurred, the cause of the failure.
*
* @discussion This method is invoked after a @link readValueForCharacteristic: @/link call, or upon receipt of a notification/indication.
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
/*!
* @method peripheral:didWriteValueForCharacteristic:error:
*
* @param peripheral The peripheral providing this information.
* @param characteristic A CBCharacteristic
object.
* @param error If an error occurred, the cause of the failure.
*
* @discussion This method returns the result of a {@link writeValue:forCharacteristic:type:} call, when the CBCharacteristicWriteWithResponse
type is used.
*/
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
/*!
* @method peripheral:didUpdateNotificationStateForCharacteristic:error:
*
* @param peripheral The peripheral providing this information.
* @param characteristic A CBCharacteristic
object.
* @param error If an error occurred, the cause of the failure.
*
* @discussion This method returns the result of a @link setNotifyValue:forCharacteristic: @/link call.
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
/*!
* @method peripheral:didDiscoverDescriptorsForCharacteristic:error:
*
* @param peripheral The peripheral providing this information.
* @param characteristic A CBCharacteristic
object.
* @param error If an error occurred, the cause of the failure.
*
* @discussion This method returns the result of a @link discoverDescriptorsForCharacteristic: @/link call. If the descriptors were read successfully,
* they can be retrieved via characteristic's descriptors
property.
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
/*!
* @method peripheral:didUpdateValueForDescriptor:error:
*
* @param peripheral The peripheral providing this information.
* @param descriptor A CBDescriptor
object.
* @param error If an error occurred, the cause of the failure.
*
* @discussion This method returns the result of a @link readValueForDescriptor: @/link call.
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error;
/*!
* @method peripheral:didWriteValueForDescriptor:error:
*
* @param peripheral The peripheral providing this information.
* @param descriptor A CBDescriptor
object.
* @param error If an error occurred, the cause of the failure.
*
* @discussion This method returns the result of a @link writeValue:forDescriptor: @/link call.
*/
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(nullable NSError *)error;
/*!
* @method peripheralIsReadyToSendWriteWithoutResponse:
*
* @param peripheral 当前设备
*
* @discussion 该方法被调用,当 writeValue:forCharacteristic:type失败,设备再次可以发送服务特征值时调用
*
*/
- (void)peripheralIsReadyToSendWriteWithoutResponse:(CBPeripheral *)peripheral;
/*!
* @method peripheral:didOpenL2CAPChannel:error:
*
* @param peripheral 当前设备
* @param channel CBL2CAPChanne
* @param error 错误信息
*
* @discussion 该方法为openL2CAPChannel: 回调
*/
- (void)peripheral:(CBPeripheral *)peripheral didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel error:(nullable NSError *)error;
当外设与手机连接成功后 会生成一个CBPeripheral对象 用以操作外设数据等读写和监听操作
上面说过 每个外设有多个service service里有包含多个特征 层层递进下来找到我们需要操作的特征按照硬件需求进行读写或监听操作 处理外设于中心设备手机的数据命令交互
写入格式 例如
//拼接命令参数 写入硬件 一般会有文档告诉你怎么拼接 (命令串(0xB2):数据(0x04))
NSMutableData *basicData = [[NSMutableData alloc]init];
UInt8 head = 0xB2;
UInt8 leng = 0x04;
UInt8 cmd = 0x0B;
UInt8 checksum = 0xC1;
[basicData appendBytes:&head length:1];
[basicData appendBytes:&leng length:1];
[basicData appendBytes:&cmd length:1];
[basicData appendBytes:&checksum length:1];
//写入命参数数据
[self.peripheral writeValue: basicData forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithResponse];
//对应的回调代理
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
关于Mac地址的获取
自iOS7之后,苹果不支持获取Mac地址,只能用UUID来标识设备,要注意的是同一个设备在不同手机上显示的UUID不相同,但有的设备可以通过 “180A”这个服务来发现特征,再来读取 “2A23”这个特征值,可以获得Mac地址。如果你的蓝牙设备不支持这样获取,你可以跟硬件工程师沟通,来获得Mac地址,添加一个获取地址命令或者增加一个含地址的特征值都可以很容易的获取。上面获取地址的前提都是需要先建立连接,如果一定要在扫描的时候获得Mac地址,让硬件工程师把数据写入广播包里,根据需求决定;
参考:
iOS中的蓝牙 CoreBluetooth蓝牙系列
CoreBluetooth 蓝牙开发(后台模式、状态保存与恢复)