因为公司做智能家居开发,有很多蓝牙的智能硬件。因此项目中经常需要和蓝牙打交道。为此为了提高开发效率,就把蓝牙的公共业务进行了封装。
本文将对封装的思路做一个简单的阐述。
首先我们需要一个头文件。在这个头文件中定义全局的宏,和结构体。看起来大概是这样的。
//包长相关的宏 #define MAX_PACK_LEN 2048 #define MAX_SEND_LEN 1024 #define MAX_SINGLE_PACKET_LEN 20 /* 新协议 命令字 begin */ /* 请求和应答协议版本命令字*/ #define REQUEST_PROTOCOL_VERSION 0X4001 #define RESPONSE_PROTOCOL_VERSION 0X0001 #define REQUEST_BIND_BLE_DEVICE 0X4002 // 请求绑定设备 #define RESPONSE_BIND_BLE_DEVICE 0X0002 // 应答 #define REQUEST_SYNC_DATA 0X4006 // 请求同步数据 #define RESPONSE_SYNC_DATA 0X0006 // 应答 #define REQUEST_KEY_DATA 0X4008 // 请求数据 #define RESPONSE_KEY_DATA 0X0008 // 应答 #define REQUEST_DELETE_DATA 0X4010 // 请求删除数据 #define RESPONSE_DELETE_DATA 0X0010 // 应答 #define REQUEST_UPGRADE_FIREWARE 0X4012 // 请求升级固件 #define RESPONSE_UPGRADE_FIREWARE 0X0012 // 应答 /* 新协议 命令字 end */ /* 协议类型 */ #define PROTOCOL_TYPE_UPGRADE 0x10 // 升级协议 #define PROTOCOL_TYPE_BUSINESS 0x00 // 业务协议 #define PROTOCOL_TYPE_BIND 0x02 // 绑定协议 //新的蓝牙协议包数据结构 typedef struct { uint8_t startflag; //起始标志 0XF2 uint8_t protocol_type; //协议类型 升级协议:0x10 业务协议:0x00 绑定协议:0x02 uint8_t protocol_version; //协议版本号 uint8_t reserved; //保留字节 uint8_t time[6]; //协议时间 年以2010为基数往上累加 uint8_t timezone; //时区 UInt16 frame_control; //操作命令字 uint16_t body_length; //数据长度 } Ble_Protocol_Head_New; typedef struct{ Ble_Protocol_Head_New* head; uint8_t *body; //包体 uint16_t packet_fcs; //FCS校验 }Ble_Protocol_Packet_New;
然后我们需要一个类来处理一些与具体业务无关的逻辑。比如错误处理,蓝牙数据收发,拆包和组包等这些。
我们姑且叫这个类为BleBaseApi吧。在我们的项目中这个只暴露了两个接口。看起来大概是这样的:
@class CLBLEBaseApi; typedef NS_ENUM (NSUInteger, CLBLEState){ CLBLEStateIdle, CLBLEStateSending, CLBLEStateReceiving, }; typedef void (^BLEScanDiscoverPeripheralsCallback) (NSArray *peripherals); @protocol CLBLEPacketHandle <NSObject> -(NSInteger)receivedPacketDataLength; -(NSInteger)totalPacketDataLength; -(NSData*)singlePacketDataDeviceResponse; //设备收到app的数据后,要给app回复一个数据,告诉app我已经收到数据 -(NSData*)singlePacketDataAppResponse; //同理App也需要回复 -(NSData*)fetchCompletePacketData; //完整的数据包 /** * 根据需要发送的数据包,做拆分,确定每一个包的数据内容 * * @return 单数数据包的内容 */ -(NSData*)fetchSinglePacketData; //拆包后的数据 /** * 解析收到的蓝牙数据 * * @param data 收到的数据 * @param error error信息 * * @return 如果解析完成,返回解析之后的值,如果解析失败返回空,error有值 * 如果解析没有完成,返回空,error也是空 */ -(NSData*)receiveAndParseData:(NSData*)data error:(NSError**)error; -(id)apiManager:(CLBLEBaseApi*)manager reformData:(NSData*)data; @end @protocol CLBLEDataSource <NSObject> @required -(NSArray*)scanServiceArray; //扫描到的服务 -(NSString*)readSeriveID; //读取数据的服务ID -(NSString*)readCharacteristicID; //读取数据的特征ID -(NSString*)writeSeriveID; -(NSString*)writeCharacteristicID; -(LGPeripheral*)connectedPeripheral; //连接到的外设 -(NSString *)broadName; //广播名 @end typedef void (^BLEProgressiveDownLoadBlock) (NSInteger totalBytesRead, NSInteger totalBytesExpected); typedef void (^BLESuccessBlock) (NSData *data); typedef void (^BLEFailBlock) (NSError* error); typedef void (^BleStateBlock)(CBCentralManagerState state); @interface CLBLEBaseApi : NSObject @property (nonatomic,weak) NSObject<CLBLEDataSource>* child; @property (nonatomic,assign) CLBLEState state; @property (nonatomic,assign) BOOL bleIsReady; -(void)setProgressiveBlock:(BLEProgressiveDownLoadBlock)progressBlock; -(void)sendBLEDataWithDataPacketProtocol:(id<CLBLEPacketHandle>)packetDelegate Success:(BLESuccessBlock)successBlock fail:(BLEFailBlock)failBlock;
其中 sendBLEDataWithDataPacketProtocol 方法处理蓝牙数据的发送接收,它的实现大概是这样子的
-(void)sendBLEDataWithDataPacketProtocol:(id<CLBLEPacketHandle>)packetDelegate Success:(BLESuccessBlock)successBlock fail:(BLEFailBlock)failBlock{ if (self.state!= CLBLEStateIdle) { NSError* error=[NSError errorWithDomain:kCLBLEManagerErrorDomain code:kBLEBusyErrorCode userInfo:@{@"message":kBLEBusyErrorCodeErrorMessage}]; failBlock(error); return; } self.child=(id <CLBLEDataSource>)self; if (!self.child.connectedPeripheral) { NSError* error=[NSError errorWithDomain:kCLBLEManagerErrorDomain code:kBLEObjErrorCode userInfo:@{@"message":kBLEObjErrorCodeErrorMessage}]; failBlock(error); return; } if (self.child.connectedPeripheral.cbPeripheral.state!=CBPeripheralStateConnected) { //NSError* error=[NSError errorWithDomain:kCLBLEManagerErrorDomain code:kBLECommunConnectErrorCode userInfo:@{@"message":@"设备没有连接,发个毛啊!"}]; NSError* error=[NSError errorWithDomain:kCLBLEManagerErrorDomain code:kBLECommunConnectErrorCode userInfo:@{@"message":kBLECommunConnectErrorCodeErrorMessage}]; failBlock(error); return; } self.dataPacketHandleDelegate=packetDelegate; [self sendBLEDataWithData:[self.dataPacketHandleDelegate fetchCompletePacketData] Success:successBlock fail:failBlock]; }
在BleBaseApi中声明了一个protocal用来获取数据包的长度以及各种数据包等,所以我们创建一个名为BleProtocal的类来负责这个事情
typedef NSData* (^fetchSinglePacketDataBlock)(); typedef NSData* (^receiveAndParseDataBlock)(NSData*data,NSError**error); typedef id (^reformDataBlock)(NSData*data); @interface CLBLEBaseProtocol : NSObject<CLBLEPacketHandle> @property(nonatomic,strong)NSData* singleDeviceResponsePacketData; @property(nonatomic,strong)NSData* singleAppResponsePacketData; @property(nonatomic,strong)NSData* packetData; @property(nonatomic,copy)fetchSinglePacketDataBlock SinglePacketDataBlock; @property(nonatomic,copy)receiveAndParseDataBlock receiveAndParseDataBlock; @property(nonatomic,copy)reformDataBlock reformDataBlock; @property(nonatomic,assign) NSInteger singlePacketTotalSend; @property(nonatomic,assign) NSInteger packetDataLength; @property(nonatomic,assign) NSInteger receiveBufferLength; @property(nonatomic,strong) NSMutableData* mutableReceiveData;
这是BleProtocal的头文件,诸如 singleDeviceResponsePacketData ,packetData 这些数据都是外界传进来的。这个文件的实现大概是这样的
-(receiveAndParseDataBlock)receiveAndParseDataBlock { __weak CLBLEBaseProtocol * weakself = self; receiveAndParseDataBlock block = ^NSData*(NSData*data,NSError**error){ if (!data) { *error=[NSError errorWithDomain:@"BLEReceiveDataHelper" code:01 userInfo:@{@"errorInfo":@"解析数据不能传空值"}]; return nil; } if (data.length<=0) { *error=[NSError errorWithDomain:@"BLEReceiveDataHelper" code:01 userInfo:@{@"errorInfo":@"解析数据不能传空值"}]; return nil; } if (weakself.receiveBufferLength==0) { uint8_t startFlag=0; [data getBytes:&startFlag length:1]; if (startFlag!=0xf2) { [self clear]; *error=[NSError errorWithDomain:@"BLEReceiveDataHelper" code:02 userInfo:@{@"errorInfo":@"起始不是0xf2"}]; return nil; } } if (!weakself.mutableReceiveData) { weakself.mutableReceiveData=[[NSMutableData alloc] initWithCapacity:0]; } [weakself.mutableReceiveData appendData:data]; weakself.receiveBufferLength+=data.length; if (weakself.receiveBufferLength>=sizeof(Ble_Protocol_Head_T)) { Ble_Protocol_Head_T bleProtocolHeaderT={0}; int offset = (int)((size_t)&(bleProtocolHeaderT.body_length)-(size_t)&bleProtocolHeaderT); typeof (weakself.packetDataLength) length; [[weakself.mutableReceiveData subdataWithRange:NSMakeRange(offset, 2)] getBytes:&length length:2]; weakself.packetDataLength = length; weakself.packetDataLength=htons(weakself.packetDataLength); weakself.packetDataLength=sizeof(Ble_Protocol_Head_T)+1+weakself.packetDataLength; if (weakself.receiveBufferLength>=weakself.packetDataLength) { NSData* resultPacketData=[weakself.mutableReceiveData subdataWithRange:NSMakeRange(0, weakself.packetDataLength)]; uint8_t endFlag=0; [[weakself.mutableReceiveData subdataWithRange:NSMakeRange(weakself.packetDataLength-1, 1)] getBytes:&endFlag length:1]; if (endFlag==0xf3) { if (weakself.receiveBufferLength==weakself.packetDataLength) { // [weakself clear]; } *error=nil; //接收完成 return resultPacketData; } else{ *error=[NSError errorWithDomain:@"BLEReceiveDataHelper" code:02 userInfo:@{@"errorInfo":@"结束标志不是0xf3"}]; return nil; } } } //接收 ing *error=nil; return nil; }; return [block copy]; } -(fetchSinglePacketDataBlock)SinglePacketDataBlock { __weak CLBLEBaseProtocol * weakself = self; fetchSinglePacketDataBlock block = ^NSData*(){ if ( weakself.singlePacketTotalSend >= weakself.packetData.length) { return nil; } int cur_send = ((self.packetData.length - _singlePacketTotalSend) > MAX_SINGLE_PACKET_LEN)? MAX_SINGLE_PACKET_LEN: (int)(self.packetData.length - self.singlePacketTotalSend); NSData* singleData=[self.packetData subdataWithRange:NSMakeRange(self.singlePacketTotalSend, cur_send)]; self.singlePacketTotalSend += cur_send; return singleData; }; return [block copy]; } #pragma mark- BLEDataReformer -(id)apiManager:(CLBLEBaseApi *)manager reformData:(NSData *)data { return self.reformDataBlock?self.reformDataBlock(data):nil; } #pragma mark- CLBLEPacketHandle -(NSInteger)receivedPacketDataLength { return self.receiveBufferLength; } -(NSInteger)totalPacketDataLength { return self.packetDataLength; } -(NSData*)singlePacketDataDeviceResponse { return self.singleDeviceResponsePacketData; } -(NSData*)singlePacketDataAppResponse { return self.singleAppResponsePacketData; } /** * 一个完整包的数据内容 * * @return 完整数据包的内容 */ -(NSData*)fetchCompletePacketData { return self.packetData; } /** * 根据需要发送的数据包,做拆分,确定每一个包的数据内容 * * @return 单数数据包的内容 */ -(NSData*)fetchSinglePacketData { return self.SinglePacketDataBlock?self.SinglePacketDataBlock():nil; } /** * 解析收到的蓝牙数据 * * @param data 收到的数据 * @param error error信息 * * @return 如果解析完成,返回解析之后的值,如果解析失败返回空,error有值 * 如果解析没有完成,返回空,error也是空 */ -(NSData*)receiveAndParseData:(NSData*)data error:(NSError**)error { return self.receiveAndParseDataBlock?self.receiveAndParseDataBlock(data,error):nil; }
因为继承了<CLBLEPacketHandle> 所以实现了他的代理,通过这样将需要的数据包和数据长度传递给BleBaseApi。
现在假设我们有一个智能床垫是蓝牙设备。我们会创建两个类分别继承自BleBaseApi和BleProtocal
我们先看看BLEMattressProtocol。它的头文件大概如下:
@interface BLEMattressProtocol :CLBLEBaseProtocol uint8_t ble_currentTimeOffset(); void ble_gmtTimeString(uint8_t* timeValue); void ble_gmtTimeString_new(uint8_t* timeValue); void Init_Ble_Protocol_Head(uint8_t *packetArray,short comdNo,uint16_t dataLength,uint8_t* uniqueID,NSString *deviceVersion); void Init_Ble_Protocol_Head_New(uint8_t *packetArray,short comdNo,uint16_t dataLength,uint8_t* uniqueID); //获取统计数据的数据包 +(BLEMattressProtocol*)getRequestStatisticalPacketData:(NSString*)uinqueIDString; //通知设备清除统计数据的数据包 +(BLEMattressProtocol*)getDeleteStatisticalPacketData:(NSString*)uinqueIDString; /** * 获取实时数据数据包 * * @param uinqueIDString 唯一ID * * @return 返回获取实时数据的数据包 */ +(BLEMattressProtocol*)getRequestRealTimePacketData:(NSString*)uinqueIDString; /** * 确认蓝牙升级的数据包 * * @param uinqueIDString 唯一ID * * @return 返回获取蓝牙升级的数据包 */ +(BLEMattressProtocol *)getRequestConfirmUpgrade:(NSString *)uinqueIDString; @end
再看看实现文件:
//获取统计数据的数据包 +(BLEMattressProtocol*)getRequestStatisticalPacketData:(NSString*)uinqueIDString{ uint8_t packetHead[sizeof(Ble_Protocol_Head_T)]={0}; // uint8_t uinqueID[6]={0}; uint8_t* uinqueID=(uint8_t*)[uinqueIDString UTF8String]; Init_Ble_Protocol_Head(packetHead, REQUEST_GET_DATA_COMMAND, 0,uinqueID,nil); NSMutableData* packetData=[[NSMutableData alloc] initWithCapacity:0]; NSData* packetHeadData=[NSData dataWithBytes:packetHead length:sizeof(Ble_Protocol_Head_T)]; [packetData appendData:packetHeadData]; uint8_t endCommand=BLE_PROTOCOL_END_FLAG; [packetData appendBytes:&endCommand length:1]; BLEMattressProtocol* obj= [[self alloc] init]; uint8_t byte[]={0}; // obj.singleAppResponsePacketData = [NSData dataWithBytes:byte length:1]; obj.singleDeviceResponsePacketData = [NSData dataWithBytes:byte length:1]; //这里就是给BleProtocal里的那些属性赋值 obj.packetData = packetData; return obj; // return packetData; }
我们再看看 MattressBLEManagerApi。下面是他的头文件。
@interface MattressBLEManagerApi : CLBLEBaseApi<CLBLEDataSource> @property (nonatomic,strong) LGPeripheral* currentPeripheral; @property (nonatomic,strong) NSString* currentBroadName; @property (nonatomic,assign) BOOL BLEIsPowerOn; //固件升级时候版本号,如1.9.0 @property (nonatomic,assign) NSString* DeviceUpgradeVersion; -(void)fetchHistoryDataWithSuccessBlock:(void(^)(NSData* data))success FailBlock:(void(^)(NSError* error))fail; -(void)fetchRealTimeDataSuccessBlock:(void(^)(NSData* data))success FailBlock:(void(^)(NSError* error))fail; -(void)fetchRealTimeDataWithInterVal:(NSTimeInterval)interval WithTimes:(NSTimeInterval)times SuccessBlock:(void(^)(NSData* data))success FailBlock:(void(^)(NSError* error))fail; -(void)fetchBLEHandShakeSuccessBlock:(void(^)(NSData* data))success FailBlock:(void(^)(NSError* error))fail; //还没有调用 -(void)deleteStatisticalDataWithSuccessBlock:(void(^)(NSData* data))success FailBlock:(void(^)(NSError* error))fail; -(void)fetchProtocolVersionSuccessBlock:(void(^)(NSData* data))success FailBlock:(void(^)(NSError* error))fail; -(void)stopFetchRealTimeData; -(void)resumeFetchRealTimeData; -(void)suspendFetchRealTimeData; @end
MattressBLEManagerApi继承了 CLBLEBaseApi<CLBLEDataSource>。通过实现 CLBLEDataSource里的代理提供了广播名,服务ID,特征ID等数据。
这个类里面我们就开始了与具体业务逻辑相关的操作了。下面看看他的实现文件。
-(void)fetchHistoryDataWithSuccessBlock:(void(^)(NSData* data))success FailBlock:(void(^)(NSError* error))fail{ NSLog(@"fetchHistoryDataWithSuccessBlock0"); NSLog(@"CLBLEStateIdle == %d",CLBLEStateIdle); NSLog(@"self.state == %d",self.state); //不相等说明蓝牙繁忙 if ( self.state != CLBLEStateIdle) { NSError* error=[NSError errorWithDomain:kCLBLEManagerErrorDomain code:kBLEBusyErrorCode userInfo:@{@"message":kBLEBusyErrorCodeErrorMessage}];; fail(error); return ; } __weak MattressBLEManagerApi* weakSelf=self; if (self.connectedPeripheral.cbPeripheral.state==CBPeripheralStateConnected) { sendRealTimeData =[BLEMattressProtocol getRequestStatisticalPacketData:self.uniqueID]; [self sendBLEDataWithDataPacketProtocol:sendRealTimeData Success:^(NSData *data) { /*历史数据比较简单,获取历史数据,上传到服务器,然后通知设备,清除数据。 然后告诉外面结果就说成功了, 业务接口自己去服务器拉取数据,整个上传数据流程完毕。 */ success(data); NSLog(@"data is %@",data); } fail:^(NSError *error) { NSLog(@"error is %@",error); fail(error); }]; } else{ NSLog(@"fetchHistoryDataWithSuccessBlock11"); self.currentPeripheral=nil; [[CLBLEManager sharedInstance] conntectPeripheralWithBLEDataSource:self withResultBlock:^(LGPeripheral *peripheral, NSError *error) { NSLog(@"fetchHistoryDataWithSuccessBlock1"); if (error) { fail(error); } else{ weakSelf.currentPeripheral=peripheral; sendRealTimeData =[BLEMattressProtocol getRequestStatisticalPacketData:self.uniqueID]; [weakSelf sendBLEDataWithDataPacketProtocol:sendRealTimeData Success:^(NSData *data) { /*历史数据比较简单,获取历史数据,上传到服务器,然后通知设备,清除数据。 然后告诉外面结果就说成功了, 业务接口自己去服务器拉取数据,整个上传数据流程完毕。 */ success(data); NSLog(@"data is %@",data); } fail:^(NSError *error) { NSLog(@"error is %@",error); fail(error); }]; } }]; } }
这一段代码是获取历史数据的接口。
除开上面那些,我们还需要对蓝牙的扫描,连接,探索,读写数据,断开等操作进行封装。所以我们创建了CLBLEManager类。他来管理这些操作。
头文件大概如下:
@protocol CLBLEManagerDelegate <NSObject> -(void)scanAllPeripherals:(NSArray *)allLGPeripherals; @end @interface CLBLEManager : NSObject @property (strong, nonatomic) LGPeripheral *currentLGPeripheral; @property (strong, nonatomic) CBCentralManager *manager; +(CLBLEManager *)sharedInstance; //初始化蓝牙并设置读取数据的回调,自动扫描,如果扫描设备名字broadName为空,则扫描所有设备并返回列表,如果broadName不为空则返回连接上的蓝牙设备 -(void)scanbleWithObject:(id<CLBLEDataSource>)child withScanTimeOut:(NSInteger)timeOut; //scanPeripheralBlock:(void (^)(NSArray *peripherals,NSError *error))scanPeripheralBlock; //连接蓝牙 //-(void)conntectPeripheral:(LGPeripheral *)peripheral withResultBlock:(void(^)(NSError *error))connectResultBlock; -(void)conntectPeripheralWithBLEDataSource:(id<CLBLEDataSource>)child withResultBlock:(void(^)(LGPeripheral* peripheral,NSError *error))connectResultBlock; //OAD空中升级的时候必须先扫描后连接 -(void)oadConntectPeripheralWithBLEDataSource:(id<CLBLEDataSource>)child withResultBlock:(void(^)(LGPeripheral* peripheral,NSError *error))connectResultBlock; //发送数据 -(void)sendData:(NSData *)data peripheral:(LGPeripheral *)peripheral; //设置Notify -(void)setNotify:(LGPeripheral *)peripheral WithCBUUID:(CBUUID *)sCBUUID cCBUUID:(CBUUID *)cCBUUID withResultBlock:(void(^)(NSError *error))resultBlock withResultBlock:(void(^)(NSData *data,NSError *error))readDataBlock; //断开蓝牙 -(void)disconntectPeripheral:(LGPeripheral *)peripheral withResultBlock:(void(^)(NSError *error))aCallback;
实现文件大体如下:
-(void)scanbleWithObject:(id<CLBLEDataSource>)child withScanTimeOut:(NSInteger)timeOut { self.broadName=nil; if ([child respondsToSelector:@selector(broadName)]) { self.broadName=[child broadName]; } if ([child connectedPeripheral].name) { self.broadName=child.connectedPeripheral.name; } self.scanServiceArray=child.scanServiceArray; self.readServiceUUID=child.readSeriveID; self.readCharacteristic=child.readCharacteristicID; self.writeServiceUUID=child.writeSeriveID; self.writeCharacteristic=child.writeCharacteristicID; if(self.readServiceUUID.length==0||self.readCharacteristic.length==0||self.writeServiceUUID.length==0||self.writeCharacteristic.length==0) { [self.multicasetDelegate scanAllPeripherals:nil]; return; } __weak CLBLEManager *weakself=self; if([LGCentralManager sharedInstance].centralReady) { [weakself scanWithTimeout:timeOut scanFinishBlock:^(NSArray *peripherals) { [weakself.multicasetDelegate scanAllPeripherals:peripherals]; }]; } self.hasConnectedPeripherals=[[NSMutableArray alloc]init]; self.retrievePeripheralsWithIdentifiers=[[NSMutableArray alloc]init]; self.scanPeripheralsCallback=nil; // Custom initialization [LGCentralManager sharedInstance].updateStateBlock =^(CBCentralManagerState state) { if (state == CBCentralManagerStatePoweredOn) { if (![LGCentralManager sharedInstance].isScanning) { [weakself scanWithTimeout:timeOut scanFinishBlock:^(NSArray *peripherals) { [weakself.multicasetDelegate scanAllPeripherals:peripherals]; }]; } } }; }
好了,到这里就结束了。以上就是我们在项目中对蓝牙业务进行封装的一个大致思路。