在上家公司待了2年,一直在做蓝牙相关的工作,也一直没有来总结关于蓝牙的文章,昨天在交流群中的小伙伴问到蓝牙相关问题,帮他看了看指出了问题,同时也发现很多东西长时间不看也都快忘记了,今天在这里总结一下,以防以后用时候重新找资料的烦恼,同时也希望能帮到有需要的朋友!
下面分享中 部分内容查看了BabyBluetooth作者的分享,部分根据当时公司的业务需求总结实战.希望能对有需要的人有所帮助
讲解分四大模块
- 基础知识了解
- app作为主设备
- app作为从设备
- BabyBluetooth入门
- 实战OTA空中升级
一.基础知识了解
蓝牙4.0的设备由于耗电低,so也称作为BLE(Bluetooth Low Energy)百度百科有关于蓝牙历史的介绍
peripheral 外设,被连接的设备为perilheral
central 发起连接的时central
service and characteristic 服务和特征 每个设备都会提供服务和特征,类似于服务端的api,但是由于结构不 同,每个外设会有很多的服务,每个服务中又包含很多字段,这些字段的权限一般分为 读read,写write,通知 notiy几种,就是我们连接设备后具体需要操作的内容。
Description 每个characteristic可以对应一个或多个Description用户描述characteristic的信息或属性
Characteristic 一个characteristic包括一个单一变量和0-n个用来描述 characteristic变量的descriptor,characteristic可以被认为是一个类型,类 似于类。
Descriptor Descriptor用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的 范围,或者一个characteristic变量特定的测量单位。
Service service是characteristic的集合
* BLE中的开发要使用CoreBluetooth框架
CoreBluetooth框架的核心其实是两个东西,peripheral和central, 可以理解成外设和中心。 对应他们分别有一组相关的API和类
以上这两组分别对应不同的业务场景,左边叫做中心模式,就是你的app作为中心,连接其他的外设的场景,而右边称为外设模式,使用手机作为 外设别其他中心设备操作的场景。
每个设备都会有一些服务,每个服务里面都会有一些特征,特征就是具体键值对,提供数据的地方。每个特征属性分为这么几种:读,写,通知 这么几种方式
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
};
外设-服务-特征 之间关系图.png
蓝牙设备的几种状态:
- 准备(standby)
- 广播(advertising)
- 监听扫描(Scanning
- 发起连接(Initiating)
- 已连接(Connected)
作为中心模式流程:
- 建立中心角色
- 扫描外设(discover)
- 连接外设(connect)
- 扫描外设中的服务和特征(discover)
- 4.1 获取外设的services
- 4.2 获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值
- 与外设做数据交互(explore and interact)
- 订阅Characteristic的通知
- 断开连接(disconnect)
作为外设模式流程:
- 启动一个Peripheral管理对象
- 本地Peripheral设置服务,特性,描述,权限等等
- Peripheral发送广告
- 设置处理订阅、取消订阅、读characteristic、写characteristic的委托方法
部分知识参考 GATT Profile 简介 有兴趣的同学可以阅读
二. app作为主设备 扫描/连接 外设 的 相关代码实现
上面说了iOS作为外设的实现流程:
- 建立中心角色
- 扫描外设(discover)
- 连接外设(connect)
- 扫描外设中的服务和特征(discover)
4.1 获取外设的services
4.2 获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值 - 与外设做数据交互(explore and interact)
- 订阅Characteristic的通知
- 断开连接(disconnect)
下面要开始动手写代码啦(🙂)
蓝牙需要真机调试,所以必须要有真机, 下载Xcode 一个蓝牙外设
// 1. 导入 CoreBluetooth 框架,和头文件
// 1.1系统蓝牙设备管理对象,可以把他理解为主设备,通过他,可以去扫描和链接外设
CBCentralManager *manager;
// 1.2 用于保存被发现设备
NSMutableArray *peripherals;
// 1.3 设置主设备的委托,CBCentralManagerDelegate
// 1.4 必须实现的: //主设备状态改变的委托,在初始化CBCentralManager的适合会打开设备,只有当设备正确打开后才能使用
- (void)centralManagerDidUpdateState:(CBCentralManager *)central; //其他选择实现的委托中比较重要的:找到外设的委托
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
//1.5 连接外设成功的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
//外设连接失败的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;
//断开外设的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;
//初始化并设置委托和线程队列,最好一个线程的参数可以为nil,默认会就main线程
manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
- 方法中 当设备开关蓝牙 都会走这个回调
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBCentralManagerStateUnknown:
NSLog(@">>>CBCentralManagerStateUnknown");
break;
case CBCentralManagerStateResetting:
NSLog(@">>>CBCentralManagerStateResetting");
break;
case CBCentralManagerStateUnsupported:
NSLog(@">>>CBCentralManagerStateUnsupported");
break;
case CBCentralManagerStateUnauthorized:
NSLog(@"CBCentralManagerStateUnauthorized");
break;
case CBCentralManagerStatePoweredOff:
NSLog(@"CBCentralManagerStatePoweredOff");
break;
case CBCentralManagerStatePoweredOn:
NSLog(@"CBCentralManagerStatePoweredOn");
//开始扫描周围的外设
/*
第一个参数nil就是扫描周围所有的外设,扫描到外设后会进入
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
第二个参数可以添加一些option,来增加精确的查找范围, 如 :
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey,
nil];
[manager scanForPeripheralsWithServices:nil options:options];
*/
[manager scanForPeripheralsWithServices:nil options:nil];
break;
default:
break;
}
}
- 扫描到设备会进入方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"当扫描到设备:%@",peripheral.name);
//接下来可以连接设备
}
- 连接外设(connect)
//扫描到设备会进入方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
//接下连接我们的测试设备,如果你没有设备,可以下载一个app叫lightbule的app去模拟一个设备
//这里自己去设置下连接规则,我设置的是P开头的设备
if ([peripheral.name hasPrefix:@"P"]){
// 一个主设备最多能连7个外设,每个外设最多只能给一个主设备连接,连接成功,失败,断开会进入各自的委托
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的委托
//找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!!
[peripherals addObject:peripheral];
//连接设备
[manager connectPeripheral:peripheral options:nil];
}
}
//连接到Peripherals-成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"连接到名称为(%@)的设备-成功",peripheral.name);
}
//连接到Peripherals-失败
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"连接到名称为(%@)的设备-失败,原因:%@",[peripheral name],[error localizedDescription]);
}
//Peripherals断开连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"外设连接断开连接 %@ \n", [peripheral name]);
}
注意 : [peripherals addObject:peripheral]; 这句代码是要手动持有外设,必须保存!
- 获取外设的服务和特征
<连接外设成功后,就要开始获取 设备的服务了,都有相应的方法和回调,扫描到结果后会进入delegate方法。但是这个委托已经不再是主设备的委托(CBCentralManagerDelegate),而是外设的委托(CBPeripheralDelegate),这个委托包含了主设备与外设交互的许多 回调方法,包括获取services,获取characteristics,获取characteristics的值,获取characteristics的Descriptor,和Descriptor的值,写数据,读rssi,用通知的方式订阅数据等等。>
// ================== 获取 外设的service ==============//
//连接到Peripherals-成功回调
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"连接到名称为(%@)的设备-成功",peripheral.name);
//设置的peripheral委托CBPeripheralDelegate
//@interface ViewController : UIViewController<CBCentralManagerDelegate,CBPeripheralDelegate>
[peripheral setDelegate:self];
//扫描外设Services,成功后会进入方法:
//-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
// 获取service时候也可以设置option 如:
// NSMutableArray *serviceUUIDs = [NSMutableArray array ];
// CBUUID *cbuuid = [CBUUID UUIDWithString:[NSString stringWithFormat:@"%x",Ble_Device_Service]];
// [serviceUUIDs addObject:cbuuid];
// [peripheral discoverServices:serviceUUIDs];
// 添加指定条件可以 提高效率
[peripheral discoverServices:nil];
}
//扫描到Services
-(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);
//扫描每个service的Characteristics,扫描到后会进入方法: -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
[peripheral discoverCharacteristics:nil forService:service];
}
}
// ==========获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值============//
//扫描到Characteristics
-(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,characteristic.value);
}
//搜索到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);
}
- 把数据写到 Characteristic 中
//写数据
-(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(@"%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(@"该字段不能写!");
}
}
- 订阅Characteristic的通知
//设置通知
-(void)notifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
//设置通知,数据通知会进入:didUpdateValueForCharacteristic方法
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
//取消通知
-(void)cancelNotifyCharacteristic:(CBPeripheral *)peripheral
characteristic:(CBCharacteristic *)characteristic{
[peripheral setNotifyValue:NO forCharacteristic:characteristic];
}
- 断开连接
//停止扫描并断开连接
-(void)disconnectPeripheral:(CBCentralManager *)centralManager
peripheral:(CBPeripheral *)peripheral{
//停止扫描
[centralManager stopScan];
//断开连接
[centralManager cancelPeripheralConnection:peripheral];
}
三. app作为外设被主设备连接
这里使用app作为一个peripheral,被其它的central连接
peripheral连接流程:
- 打开peripheralManager,设置peripheralManager的delegate
- 创建characteristics,characteristics的description 创建service,把characteristics添加到service中,再把service添加到peripheralManager中
- 开启广播advertising
- 对central的操作进行响应
- 4.1 读characteristics请求
- 4.2 写characteristics请求
- 4.4 订阅和取消订阅characteristics
具体步骤核心代码:
<打开peripheralManager,设置peripheralManager的委托>
<初始化peripheralManager, 这里的peripheralManager和CBCentralManager 很相似>
peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
<创建characteristics 创建service,把characteristics添加到service 然后把service添加到peripheralManager>
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
// 在这里判断蓝牙的状态, 因为蓝牙打开成功后才能配置service和characteristics
[self config];
}
<配置相关service和>
//配置蓝牙的服务和特征
-(void)config{
//特征字段描述
CBUUID *CBUUIDCharacteristicUserDescriptionStringUUID = [CBUUID UUIDWithString:CBUUIDCharacteristicUserDescriptionString];
/*
可以通知的Characteristic
properties:CBCharacteristicPropertyNotify
permissions CBAttributePermissionsReadable
*/
CBMutableCharacteristic *notiyCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:notiyCharacteristicUUID] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];
/*
可读写的characteristics
properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead
permissions CBAttributePermissionsReadable | CBAttributePermissionsWriteable
*/
CBMutableCharacteristic *readwriteCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readwriteCharacteristicUUID] properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable | CBAttributePermissionsWriteable];
//设置description
CBMutableDescriptor *readwriteCharacteristicDescription1 = [[CBMutableDescriptor alloc]initWithType: CBUUIDCharacteristicUserDescriptionStringUUID value:@"name"];
[readwriteCharacteristic setDescriptors:@[readwriteCharacteristicDescription1]];
/*
只读的Characteristic
properties:CBCharacteristicPropertyRead
permissions CBAttributePermissionsReadable
*/
CBMutableCharacteristic *readCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:readCharacteristicUUID] properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];
//service1初始化并加入两个characteristics
CBMutableService *service1 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID1] primary:YES];
[service1 setCharacteristics:@[notiyCharacteristic,readwriteCharacteristic]];
//service2初始化并加入一个characteristics
CBMutableService *service2 = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID2] primary:YES];
[service2 setCharacteristics:@[readCharacteristic]];
//添加后就会调用代理的- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
[peripheralManager addService:service1];
[peripheralManager addService:service2];
}
<开启广播>
//perihpheral添加了service
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
if (error == nil) {
serviceNum++;
}
//因为我们添加了2个服务,所以想两次都添加完成后才去发送广播
if (serviceNum==2) {
//添加服务后可以在此向外界发出通告 调用完这个方法后会调用代理的
//(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error
[peripheralManager startAdvertising:@{
CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:ServiceUUID1],[CBUUID UUIDWithString:ServiceUUID2]],
CBAdvertisementDataLocalNameKey : LocalNameKey
}
];
}
}
//peripheral开始发送advertising
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
NSLog(@"in peripheralManagerDidStartAdvertisiong");
}
<对central的操作进行响应>
1 读characteristics请求 2 写characteristics请求 3 订阅和取消订阅characteristics
//订阅characteristics
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@"订阅了 %@的数据",characteristic.UUID);
//每秒执行一次给主设备发送一个当前时间的秒数
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(sendData:) userInfo:characteristic repeats:YES];
}
//取消订阅characteristics
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@"取消订阅 %@的数据",characteristic.UUID);
//取消回应
[timer invalidate];
}
//发送数据,发送当前时间的秒数
-(BOOL)sendData:(NSTimer *)t {
CBMutableCharacteristic *characteristic = t.userInfo;
NSDateFormatter *dft = [[NSDateFormatter alloc]init];
[dft setDateFormat:@"ss"];
NSLog(@"%@",[dft stringFromDate:[NSDate date]]);
//执行回应Central通知数据
return [peripheralManager updateValue:[[dft stringFromDate:[NSDate date]] dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:(CBMutableCharacteristic *)characteristic onSubscribedCentrals:nil];
}
//读characteristics请求
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
NSLog(@"didReceiveReadRequest");
//判断是否有读数据的权限
if (request.characteristic.properties & CBCharacteristicPropertyRead) {
NSData *data = request.characteristic.value;
[request setValue:data];
//对请求作出成功响应
[peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
}else{
[peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
}
}
//写characteristics请求
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests{
NSLog(@"didReceiveWriteRequests");
CBATTRequest *request = requests[0];
//判断是否有写数据的权限
if (request.characteristic.properties & CBCharacteristicPropertyWrite) {
//需要转换成CBMutableCharacteristic对象才能进行写值
CBMutableCharacteristic *c =(CBMutableCharacteristic *)request.characteristic;
c.value = request.value;
[peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
}else{
[peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
}
}
四. 开源库 BabyBluetooth介绍
有需要的同学可以看看BabyBluetooth源码
BabyBluetooth的优缺点
BabyBluetooth使用block方法,可以一定程度上节省开发时间,节约成本,毕竟直接用官方的框架写还是比较麻烦的,
当然这还要根据自己的实际情况来,当时由于我所在公司需要自己的SDK,根据当时的业务情况,只能我自己来写了,
而BabyBluetooth链式的语法写起来比较清爽. 如果公司有一些业务上的需求BabyBluetooth没有实现,
等待作者更新是不太现实的,这时候自己来动手开发一个吧!
使用步骤可以参考 :
iOS蓝牙篇之BabyBluetooth详解
iOS蓝牙开发:解析BabyBluetooth
iOS 蓝牙库BabyBluetooth的使用笔记
五. OTA(空中升级实战)
当时的业务需求是 :
读取地锁当前硬件的版本号,判断是否需要升级
如果需要升级,手机连接地锁后给地锁传输脚本给地锁中的硬件进行升级
<读取硬件版本号 要连接上外设后 读取的characteristic的值>
在回调方法 :
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;
中处理
版本号.png
- 空中升级
这里会借助一个第三方的开源库 : Nordic GitHub
DFUFirmware *selectedFirmware = [[DFUFirmware alloc] initWithUrlToZipFile:self.filePath];
self.initiator = [[DFUServiceInitiator alloc] initWithCentralManager: self.centralManager target:self.peripheral];
[self.initiator withFirmwareFile:selectedFirmware];
self.initiator.delegate = self; // - to be informed about current state and errors
self.initiator.logger = self;
self.initiator.progressDelegate = self;
self.dfuController = [self.initiator start];
👆需要的代理:
#pragma mark DFUServiceDelegate
- (void)onUploadProgress:(NSInteger)part totalParts:(NSInteger)totalParts progress:(NSInteger)progress currentSpeedBytesPerSecond:(double)currentSpeedBytesPerSecond avgSpeedBytesPerSecond:(double)avgSpeedBytesPerSecond{
NSLog(@" %s %.2ld",__func__,(long)progress);
[self.delegate onFSMOtaUpgradeProgress:progress];
}
- (void)didErrorOccur:(enum DFUError)error withMessage:(NSString *)message{
switch (error) {
case DFUErrorRemoteSuccess:
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteSuccess"]];
break;
case DFUErrorRemoteInvalidState :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteInvalidState"]];
break;
case DFUErrorRemoteNotSupported :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteNotSupported"]];
break;
case DFUErrorRemoteDataExceedsLimit :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteDataExceedsLimit"]];
break;
case DFUErrorRemoteCrcError :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteCrcError"]];
break;
case DFUErrorRemoteOperationFailed:
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorRemoteOperationFailed"]];
break;
case DFUErrorFileNotSpecified:
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFileNotSpecified"]];
break;
case DFUErrorFileInvalid :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFileInvalid"]];
break;
case DFUErrorExtendedInitPacketRequired :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorExtendedInitPacketRequired"]];
break;
case DFUErrorInitPacketRequired :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorInitPacketRequired"]];
break;
case DFUErrorFailedToConnect :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorFailedToConnect"]];
break;
case DFUErrorDeviceDisconnected:
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorDeviceDisconnected"]];
break;
case DFUErrorServiceDiscoveryFailed:
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorServiceDiscoveryFailed"]];
break;
case DFUErrorDeviceNotSupported :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorDeviceNotSupported"]];
break;
case DFUErrorReadingVersionFailed :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorReadingVersionFailed"]];
break;
case DFUErrorEnablingControlPointFailed :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorEnablingControlPointFailed"]];
break;
case DFUErrorWritingCharacteristicFailed :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorWritingCharacteristicFailed"]];
break;
case DFUErrorReceivingNotificationFailed :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorReceivingNotificationFailed"]];
break;
case DFUErrorUnsupportedResponse :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorUnsupportedResponse"]];
break;
case DFUErrorBytesLost :
[self.delegate onFSMOtaUpgradeResult:[NSString stringWithFormat:@"DFUErrorBytesLost"]];
break;
default:
break;
}
NSLog(@" %s 升级失败 错误的信息是: %@",__func__ ,message);
NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) firstObject];
[savePath stringByAppendingPathComponent:@"ble_app_wechat_debug_v1.1.zip"];
[self disConnectDevice:self.lockId];
NSError *err;
NSFileManager *fileMgr = [NSFileManager defaultManager];
[fileMgr removeItemAtPath:savePath error:&err];
}
#pragma mark LoggerDelegate 代理
- (void)logWith:(enum LogLevel)level message:(NSString * _Nonnull)message{
// NSLog(@"消息是 : %@",message);
}
- (void)didStateChangedTo:(enum DFUState)state{
switch (state) {
case DFUStateAborted:{
[self otaCallback:@"DFUStateAborted"];
break;
}
case DFUStateConnecting:{
[self otaCallback:@"DFUStateConnecting"];
break;
}
case DFUStateDisconnecting:{
[self otaCallback:@"DFUStateDisconnecting"];
break;
}
case DFUStateEnablingDfuMode:{
[self otaCallback:@"DFUStateEnablingDfuMode"];
break;
}
case DFUStateStarting:{
[self otaCallback:@"DFUStateStarting"];
break;
}
case DFUStateUploading:{
[self otaCallback:@"DFUStateUploading"];
break;
}
case DFUStateValidating:{
[self otaCallback:@"DFUStateValidating"];
break;
}
case DFUStateCompleted:{
[self.centralManager cancelPeripheralConnection:self.peripheral];
[self otaCallback:@"DFUStateCompleted"];
NSLog(@"%d",self.dfuController.paused);
break;
}
default:
break;
}
}
//OTA升级的回调
- (void)otaCallback:(NSString *)state{
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(onFSMOtaUpgradeResult:)])
{
[self.delegate onFSMOtaUpgradeResult:state];
}
});
}
网友评论