蓝牙架构的搭建
-
前言:笔者认为,如果只是单纯的传授大家代码怎么敲,那么大家很有可能在实际开发中难以运用。刚好本人曾经参与过多款智能硬件开发的架构搭建,本小节本人就现场带领大家开发出一个通用的蓝牙工具类
- 既然是工具类,虽然大家以后可以在开发中直接拿去用,但是我的目的是想要传授给大家架构的思想,而不是教大家如何偷懒
- 为了能够让大家对蓝牙通讯理解的更加的透彻,本人专门买了一个小米手环,并且经过大量的测试,破解了部分小米的蓝牙协议(小米手环蓝牙数据是没有加密的)
- 只有对技术执着的追求,才能造就更高的品质
-
目前该工具类由于时间原因,主要是想让大家熟悉蓝牙开发的流程,并没有对更深层次的架构做研究
- 1.只支持蓝牙与外设一对一连接,一对多未做复杂处理
- 一般开发中,一对一够用了
- 2.对API接口的架构没有做深入的探讨研究
- 关于类的接口设计,将会在下一个课程阶段
实用技术项目阶段
再给大家重点介绍,目前大家也很难吸收太难的知识点
- 关于类的接口设计,将会在下一个课程阶段
- 3.本人将会在后期继续优化我们的框架,并且放入
github
供全球的iOS开发朋友们使用
- 中国软件的下一步发展就是要走向国际一流
- 1.只支持蓝牙与外设一对一连接,一对多未做复杂处理
-
HMBluetoothManager.h文件
#import <CoreBluetooth/CoreBluetooth.h> #define kHMBluetoothManager [HMBluetoothManager shareInstance] @interface HMBluetoothManager : NSObject //单利类实现 +(HMBluetoothManager*)shareInstance; //蓝牙中心 @property(nonatomic,strong)CBCentralManager *CB_central; //蓝牙外设数组,蓝牙支持一对多连接(一个中心 多个外设) //扫描到的外设数组 @property(nonatomic,strong)NSMutableArray <CBPeripheral *>*scanArr; //已经连接的外设数组 @property(nonatomic,strong)NSMutableArray <CBPeripheral *>*connectArr; //扫描外设成功回调 @property(nonatomic,copy)void(^scanPeripheralUpdate)(CBPeripheral *peripheral); //连接外设成功回调 @property(nonatomic,copy)void(^connectedPeripheral)(CBPeripheral *peripheral,NSString *connectState); //当前激活的外设(目前的架构暂时只支持一对一连接) @property(nonatomic,strong)CBPeripheral *currentPeripheral; //当前蓝牙特征(发送数据) @property (strong ,nonatomic) CBCharacteristic *currentCharacteristic; //当前发送数据的UUID @property(nonatomic,strong)NSString *UUID; /** 检测蓝牙是否可用 @param completion 错误表示 可用标签 @return 可用标签 */ - (BOOL)isDeviceBluetoothAvaliable:(void(^)(NSError *error,BOOL flag))completion; /** 开始扫描 */ - (void)BeginScanPeripheral:(void(^)(CBPeripheral *peripheral))scanPeripheralUpdate; /** 连接外设 @param peripheral 外设 @param connectedPeripheral 连接回调 */ - (void)connectPeripheral:(CBPeripheral *)peripheral Completion:(void(^)(CBPeripheral *peripheral,NSString *connectState))connectedPeripheral; /** 发送数据 @param value 数据 @param peripheral 外设 @param characteristic 特征 */ - (void)writeValue:(NSData *)value toPeripheral:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic; @end
- HMBluetoothManager.m文件
#import "HMBluetoothManager.h" //蓝牙框架 #import <CoreBluetooth/CoreBluetooth.h> #define kDisconnectPeripheralNotification @"kDisconnectPeripheralNotification" const NSString *connectStateSuccess = @"连接成功"; const NSString *connectStateRepeat = @"外设已经在连接列表中"; const NSString *connectStateFaild = @"连接失败"; @interface HMBluetoothManager ()<CBCentralManagerDelegate,CBPeripheralDelegate> @end @implementation HMBluetoothManager //单利类实现 +(HMBluetoothManager*)shareInstance { static HMBluetoothManager *manager = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ manager = [[HMBluetoothManager alloc] init]; }); return manager; } - (instancetype)init { self = [super init]; //初始化数组 self.scanArr = [NSMutableArray array]; self.connectArr = [NSMutableArray array]; return self; } #pragma mark - 检测蓝牙是否可用 /** 检测蓝牙是否可用 @param completion 错误表示 可用标签 @return 可用标签 */ - (BOOL)isDeviceBluetoothAvaliable:(void(^)(NSError *error,BOOL flag))completion { NSString * state = nil; BOOL flag = NO; switch ([self.CB_central state]) { case CBCentralManagerStateUnsupported: state = @"系统不支持蓝牙."; break; case CBCentralManagerStateUnauthorized: state = @"手机蓝牙未开启"; break; case CBCentralManagerStatePoweredOff: state = @"Bluetooth is currently powered off."; break; case CBCentralManagerStatePoweredOn: state = @"蓝牙可用"; flag = YES; break; case CBCentralManagerStateUnknown: flag = YES; state = @"未知错误"; break; default: break; } if ([state isEqualToString:@"未知错误"]) { //第一次开启扫描可能会出现未知错误,只需要再次调用扫描即可 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.CB_central scanForPeripheralsWithServices:nil options:nil]; }); } /**创建error *domain:作用域,表示error报错的位置 * code:错误码 * userInfo:错误描述键值对,key一般使用系统默认NSLocalizedDescriptionKey */ NSError *error = [NSError errorWithDomain:@"HMBluetoothManager" code:-1 userInfo:@{NSLocalizedDescriptionKey:state}]; //回调block,非空判断不要忘记 if (completion) { completion(error,flag); } return flag; } #pragma mark - 1.开始扫描 - (void)BeginScanPeripheral:(void(^)(CBPeripheral *peripheral))scanPeripheralUpdate { //1.创建蓝牙中心 if (!self.CB_central) { //参数是代理 和线程) self.CB_central = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()]; } //2.判断当前设备是否支持蓝牙 //NSAssert:第一个参数condition表示判断条件 第二个参数desc表示描述。当判断条件不成立时程序会崩溃并且打印描述 NSAssert([self isDeviceBluetoothAvaliable:nil], @"手机不支持蓝牙"); self.scanPeripheralUpdate = scanPeripheralUpdate; //3.开始扫描 /** Services:查找指定的外设,不设置表示查找所有的外设 options:查找方式,一般设为nil使用系统默认 */ [self.CB_central scanForPeripheralsWithServices:nil options:nil]; } #pragma mark -3.连接外设 - (void)connectPeripheral:(CBPeripheral *)peripheral Completion:(void(^)(CBPeripheral *peripheral,NSString *connectState))connectedPeripheral { self.connectedPeripheral = connectedPeripheral; //如果是已经连接,则直接返回 if (peripheral.state == CBPeripheralStateConnected) { connectedPeripheral(peripheral,connectStateRepeat); } else { [self.CB_central connectPeripheral:peripheral options:nil]; } } #pragma mark -蓝牙中心代理 //蓝牙中心有更新状态 - (void)centralManagerDidUpdateState:(CBCentralManager *)central { } #pragma mark - 2.扫描到外设 //查到外设后,停止扫描,连接设备 -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { //外设相关数据 NSLog(@"%@",advertisementData); //外设唯一标识符 NSLog(@"%@",peripheral.identifier); //外设的名称 NSLog(@"%@",peripheral.name); //与外设的信号强度 NSLog(@"%@",RSSI); self.scanPeripheralUpdate(peripheral); //添加到扫描数组(工具类的封装,不应该在内部处理与自身无关的业务逻辑,所以这里不要连接设备,应该封装连接方法让外部调用) //添加之前做一个重复判断,避免同一外设被多次添加 if (![self.scanArr containsObject:peripheral]) { [self.scanArr addObject:peripheral]; } } #pragma mark - 4.连接外设成功,开始寻找服务 //连接外设成功,开始发现服务 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { self.connectedPeripheral(peripheral,connectStateSuccess); //设为当前连接的外设 self.currentPeripheral = peripheral; //添加到已经连接的数组 [self.connectArr addObject:peripheral]; //设置代理 [peripheral setDelegate:self]; //发现服务 [peripheral discoverServices:nil]; } //连接外设失败 -(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { self.connectedPeripheral(peripheral,connectStateFaild); } //连接断开 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { //这里应该主动发送通知告知外部 [[NSNotificationCenter defaultCenter] postNotificationName:kDisconnectPeripheralNotification object:nil userInfo:@{@"key":peripheral}]; //断开连接之后,应当从连接列表中移除外设 [self.connectArr removeObject:peripheral]; // We're disconnected, so start scanning again } #pragma mark CBPeripheralDelegate 外设代理 #pragma mark- 5.发现服务,搜索特征 -(void) peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ if (error) { NSLog(@"Error discovering services: %@", [error localizedDescription]); return; } int i=0; // for (CBService *s in peripheral.services) { // [self.nServices addObject:s]; // } //遍历服务,发现特征 for (CBService *s in peripheral.services) { NSLog(@"%@",[NSString stringWithFormat:@"%d :服务 UUID: %@(%@)",i,s.UUID.data,s.UUID]); i++; [peripheral discoverCharacteristics:nil forService:s]; } } #pragma mark- 6.已搜索到某个服务的特征Characteristics -(void) peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{ // Deal with errors (if any) if (error) { NSLog(@"Error discovering characteristics: %@", [error localizedDescription]); return; } //从服务中遍历特征 for (CBCharacteristic *c in service.characteristics) { NSLog(@"%@",[NSString stringWithFormat:@"发现特征的服务UUID:%@ 该特征UUID:%@",service.UUID ,c.UUID]); //如果是当前发送数据的UUID,则保存该特征 if ([[c.UUID UUIDString] isEqual:self.UUID]) { self.currentCharacteristic = c; } //开启与特征之间的通知(中心与外设长连接,当特征发送数据过来时,能够及时收到) [peripheral setNotifyValue:YES forCharacteristic:c]; //读取特征服务,一次性 // [peripheral readValueForCharacteristic:c]; } } #pragma mark- 获取外设发来的数据,不论是read和notify,获取数据都是从这个方法中读取。 - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { NSLog(@"外设发送过来的数据:%@",characteristic.value.description ); } #pragma mark- 中心读取外设实时数据 - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { if (error) { } // Notification has started if (characteristic.isNotifying) { //读取外设数据 [peripheral readValueForCharacteristic:characteristic]; NSLog(@"%@",characteristic.value); } else { // Notification has stopped // so disconnect from the peripheral // NSLog(@"Notification stopped on %@. Disconnecting", characteristic); } } #pragma mark - 7.给特征发送数据 //把数据写到哪个外设的哪个特征里面 - (void)writeValue:(NSData *)value toPeripheral:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic { //写入数据 [peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse]; } @end