前言:
上一篇文章講了iOS藍牙開發的基礎與Demo的應用http://www.lxweimin.com/p/6e079da2370c, 這一章講講藍牙在項目中實戰的.
藍牙Demo鏈接地址: https://github.com/iOSzhangkai/BLE4.0_iOS
工程環境
(1) xcode 8.2
(2)iOS 10.1
前期思路
在項目實施之前處于藍牙學習階段,對于藍牙的理解只處于可以使用,,知道這是藍牙...藍牙內部方法,底層實現原理完全懵逼,大寫的懵逼,所以在demo中將藍牙方法整個粘入控制器中,經過惡心而又漫長的原始階段終于將藍牙外設與手機連接給整出來了.....但是因為iOS提供藍牙外設連接的<CoreBluetooth/CoreBluetooth.h>
中的所以方法都是通過代理的實現,導致項目的控制器頁面代碼行數多達兩千行,這對于一個程序猿是個恥辱啊!!![一個很小的功能],在項目上線后的幾周陸續看了許多大牛的藍牙封裝類,決定自己要改造這個控制器代碼,有寫的不到之處希望各位可以指出,共同進步.
*廢話不多說 直接上代碼 *
藍牙管理類的.h
#import <Foundation/Foundation.h>
//BLIE4.0 藍牙庫
#import <CoreBluetooth/CoreBluetooth.h>
//藍牙連接狀態的定義
#define kCONNECTED_UNKNOWN_STATE @"未知藍牙狀態"
#define kCONNECTED_RESET @"藍牙重置"
#define kCONNECTED_UNSUPPORTED @"該設備不支持藍牙"
#define kCONNECTED_UNAUTHORIZED @"未授權藍牙權限"
#define kCONNECTED_POWERED_OFF @"藍牙已關閉"
#define kCONNECTED_POWERD_ON @"藍牙已打開"
#define kCONNECTED_ERROR @"未知的藍牙錯誤"
/**
設備名稱
*/
#define PMServiceName @"Bozonn-Air01"
/**
設備UUID
*/
#define PMServiceUUID @"FFE0"
/**
設備配置UUID
*/
#define PMServiceUUID_Config @"00002902-0000-1000-8000-00805f9b34fb"
/**
設備讀取UUID
*/
#define PMServiceUUID_Receive @"0000ffe1-0000-1000-8000-00805f9b34fb"
/**
設備寫入UUID
*/
#define PMServiceUUID_Send @"0000ffe1-0000-1000-8000-00805f9b34fb"
/**
藍牙鏈接狀態
@param state 狀態
*/
typedef void (^BLELinkBlock)(NSString *state);
/**
藍牙返回數據
@param array 返回數據
*/
typedef void (^BLEDataBlock)(NSMutableArray *array);
typedef enum BLEState_NOW{
BLEState_Successful = 0,//連接成功
BLEState_Disconnect = 1, // 失敗
BLEState_Normal, // 未知
}BLEState_NOW;
/**
藍牙連接成功 或者斷開
*/
typedef void(^BLEStateBlcok)(int number);
@interface BLEModel : NSObject
@property (nonatomic,copy) NSString *connectState;//藍牙連接狀態
@property (nonatomic,copy) BLELinkBlock linkBlcok;
@property (nonatomic,copy) BLEDataBlock dataBlock;
@property (nonatomic,copy) BLEStateBlcok stateBlock;
/**
主動斷開鏈接
*/
-(void)cancelPeripheralConnection;
/**
發送命令
*/
- (void) sendData:(NSData *)data;
藍牙管理類.m
#import "BLEModel.h"
#import "BLEIToll.h"
@interface BLEModel ()<CBCentralManagerDelegate,CBPeripheralDelegate>
/**
* 藍牙連接必要對象
*/
@property (nonatomic, strong) CBCentralManager *centralMgr;
@property (nonatomic, strong) CBPeripheral *discoveredPeripheral;
@property (strong, nonatomic) CBCharacteristic* writeCharacteristic;
@property (nonatomic,assign) BOOL isInitiativeDisconnect;//主動斷開連接
@end
@implementation BLEModel
- (instancetype)init
{
self = [super init];
if (self) {
_centralMgr = [[CBCentralManager alloc]initWithDelegate:self queue:nil];
}
return self;
}
/**
* 停止掃描
*/
-(void)stopScan
{
[_centralMgr stopScan];
}
#pragma mark -- CBCentralManagerDelegate
#pragma mark- 掃描設備,連接
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(@"name:%@",peripheral);
/**
當掃描到藍牙數據為空時,停止掃描
*/
if (!peripheral || !peripheral.name || ([peripheral.name isEqualToString:@""])) {
return;
}
/**
當掃描到服務UUID與設備UUID相等時,進行藍牙與設備鏈接
*/
if ((!self.discoveredPeripheral || (self.discoveredPeripheral.state == CBPeripheralStateDisconnected))&&([peripheral.name isEqualToString:PMServiceName])) {
self.discoveredPeripheral = [peripheral copy];
//self.peripheral.delegate = self;
NSLog(@"connect peripheral: %@",peripheral);
[self.centralMgr connectPeripheral:peripheral options:nil];
}
}
#pragma mark - 藍牙的狀態
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBManagerStateUnknown:
{
//NSLog(@"無法獲取設備的藍牙狀態");
self.connectState = kCONNECTED_UNKNOWN_STATE;
}
break;
case CBManagerStateResetting:
{
//NSLog(@"藍牙重置");
self.connectState = kCONNECTED_RESET;
}
break;
case CBManagerStateUnsupported:
{
//NSLog(@"該設備不支持藍牙");
self.connectState = kCONNECTED_UNSUPPORTED;
}
break;
case CBManagerStateUnauthorized:
{
//NSLog(@"未授權藍牙權限");
self.connectState = kCONNECTED_UNAUTHORIZED;
}
break;
case CBManagerStatePoweredOff:
{
//NSLog(@"藍牙已關閉");
self.connectState = kCONNECTED_POWERED_OFF;
}
break;
case CBManagerStatePoweredOn:
{
//NSLog(@"藍牙已打開");
self.connectState = kCONNECTED_POWERD_ON;
[_centralMgr scanForPeripheralsWithServices:nil options:nil];
}
break;
default:
{
//NSLog(@"未知的藍牙錯誤");
self.connectState = kCONNECTED_ERROR;
}
break;
}
self.linkBlcok(self.connectState);
//[self getConnectState];
}
#pragma park- 連接成功,掃描services
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
if (!peripheral) {
return;
}
[self.centralMgr stopScan];
self.stateBlock(BLEState_Successful);
NSLog(@"peripheral did connect: %@",peripheral);
[self.discoveredPeripheral setDelegate:self];
[self.discoveredPeripheral discoverServices:nil];
}
#pragma mark - 掃描service
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
NSArray *services = nil;
if (peripheral != self.discoveredPeripheral) {
NSLog(@"Wrong Peripheral.\n");
return ;
}
if (error != nil) {
NSLog(@"Error %@\n", error);
return ;
}
services = [peripheral services];
NSLog(@"%@",services);
if (!services || ![services count]) {
NSLog(@"No Services");
return ;
}
for (CBService *service in services) {
NSLog(@"該設備的service:%@",service);
/*
>
*/
if ([[service.UUID UUIDString] isEqualToString:PMServiceUUID]) {
[peripheral discoverCharacteristics:nil forService:service];
return ;
}
}
}
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error)
{
NSLog(@"didDiscoverCharacteristicsForService error : %@", [error localizedDescription]);
return;
}
for (CBCharacteristic *c in service.characteristics)
{
NSLog(@"\n>>>\t特征UUID FOUND(in 服務UUID:%@): %@ (data:%@)",service.UUID.description,c.UUID,c.UUID.data);
/**
>>> 特征UUID FOUND(in 服務UUID:FFE0): FFE1 (data:<ffe1>)
>>> 特征UUID FOUND(in 服務UUID:FFE0): FFE2 (data:<ffe2>)
*/
/*
根據特征不同屬性去讀取或者寫
if (c.properties==CBCharacteristicPropertyRead) {
}
if (c.properties==CBCharacteristicPropertyWrite) {
}
if (c.properties==CBCharacteristicPropertyNotify) {
}
*/
/*
設備讀取UUID
*/
if ([c.UUID isEqual:[CBUUID UUIDWithString:PMServiceUUID_Receive]]) {
self.writeCharacteristic = c;
[_discoveredPeripheral setNotifyValue:YES forCharacteristic:self.writeCharacteristic];
}
/**
設備寫入UUID
*/
if ([c.UUID isEqual:[CBUUID UUIDWithString:PMServiceUUID_Send]]) {
self.writeCharacteristic = c;
[_discoveredPeripheral setNotifyValue:YES forCharacteristic:self.writeCharacteristic];
}
}
}
#pragma mark - 讀取數據
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error)
{
NSLog(@"didUpdateValueForCharacteristic error : %@", error.localizedDescription);
return;
}
NSData *data = characteristic.value;
NSMutableArray *dataArr = [BLEIToll convertDataToHexStr:data];
self.dataBlock(dataArr);
}
#pragma mark- 外設斷開連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
NSLog(@"外設斷開連接 %@: %@\n", [peripheral name], [error localizedDescription]);
self.stateBlock(BLEState_Disconnect);
//重連外設
if (!self.isInitiativeDisconnect) {
[self.centralMgr connectPeripheral:peripheral options:nil];
}
}
#pragma mark - 主動斷開連接
-(void)cancelPeripheralConnection{
self.isInitiativeDisconnect = YES;
if (self.discoveredPeripheral) {//已經連接外設,則斷開
[self.centralMgr cancelPeripheralConnection:self.discoveredPeripheral];
}else{//未連接,則停止搜索外設
[self.centralMgr stopScan];
}
}
/**
發送命令
*/
- (void) sendData:(NSData *)data{
/**
通過CBPeripheral 類 將數據寫入藍牙外設中,藍牙外設所識別的數據為十六進制數據,在ios系統代理方法中將十六進制數據改為 NSData 類型 ,但是該數據形式必須為十六進制數 0*ff 0*ff格式 在iToll中有將 字符串轉化為 十六進制 再轉化為 NSData的方法
*/
[self.discoveredPeripheral writeValue:data forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithoutResponse];
}
//向peripheral中寫入數據后的回調函數
- (void)peripheral:(CBPeripheral*)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
//該方法可以監聽到寫入外設數據后的狀態
if (error) {
NSLog(@"didWriteValueForCharacteristic error : %@", error.localizedDescription);
return;
}
NSLog(@"write value success : %@", characteristic);
}
食用方法
#import "BLEModel.h"
@property (nonatomic,strong) BLEModel *manager;
- (void)BluetoothConnection{
/**
BLEModel 對象通過init方法創建 ,在init方法中就已經將藍牙操作對象進行初始化,遵循代理,在控制器中無需遵循任何的藍牙代理,引入藍牙庫
*/
_manager = [[BLEModel alloc]init];
/**
BLEModel 中的 BLELinkBlock 是將藍牙當前狀態進行回調,方便在項目中對藍牙處于什么狀態進行操作
*/
_manager.linkBlcok = ^(NSString *state){
NSLog(@"%@",state);
};
/**
BLEModel 中的BLEStateBlcok 是手機與外設鏈接狀態的回調,
*/
_manager.stateBlock = ^(int number){
NSLog(@"%d",number);
};
/**
BLEModel 中的BLEDataBlock 是外設返回數據到手機
外設返回的數據一般為十六進制數據,在BLEModel 內部中已經將十六進制數據進行一次處理,把拿到的數據已字符串的類型添加到array中,所以可以直接使用對array進行操作,無需再做處理
*/
_manager.dataBlock = ^(NSMutableArray *array){
NSLog(@"%@",array);
}
};
/*
設備給藍牙傳輸數據 必須以十六進制數據傳給藍牙 藍牙設備才會執行
因為iOS 藍牙庫中方法 傳輸書記是以NSData形式
因此封裝 stringToByte 方法的作用字符串 ---> 十六進制數據 ---> NSData數據
發送一個 0xFA 0xE4 Ox33 的命令
*/
NSData *data = [[BLEIToll alloc] stringToByte:@"FAE433"];
NSLog(@"寫入數據%@",data);
[_manager sendData:data];
}
/**
* 設備給藍牙傳輸數據 必須以十六進制數據傳給藍牙 藍牙設備才會執行
因為iOS 藍牙庫中方法 傳輸書記是以NSData形式 這個方法 字符串 ---> 十六進制數據 ---> NSData數據
*
* @param string 傳入字符串命令
*
* @return 將字符串 ---> 十六進制數據 ---> NSData數據
*/
-(NSData*)stringToByte:(NSString*)string
{
NSString *hexString=[[string uppercaseString] stringByReplacingOccurrencesOfString:@" " withString:@""];
if ([hexString length]%2!=0) {
return nil;
}
Byte tempbyt[1]={0};
NSMutableData* bytes=[NSMutableData data];
for(int i=0;i<[hexString length];i++)
{
unichar hex_char1 = [hexString characterAtIndex:i]; ////兩位16進制數中的第一位(高位*16)
int int_ch1;
if(hex_char1 >= '0' && hex_char1 <='9')
int_ch1 = (hex_char1-48)*16; //// 0 的Ascll - 48
else if(hex_char1 >= 'A' && hex_char1 <='F')
int_ch1 = (hex_char1-55)*16; //// A 的Ascll - 65
else
return nil;
i++;
unichar hex_char2 = [hexString characterAtIndex:i]; ///兩位16進制數中的第二位(低位)
int int_ch2;
if(hex_char2 >= '0' && hex_char2 <='9')
int_ch2 = (hex_char2-48); //// 0 的Ascll - 48
else if(hex_char2 >= 'A' && hex_char2 <='F')
int_ch2 = hex_char2-55; //// A 的Ascll - 65
else
return nil;
tempbyt[0] = int_ch1+int_ch2; ///將轉化后的數放入Byte數組里
[bytes appendBytes:tempbyt length:1];
}
return bytes;
}
小結
在開發中將BLEModel.h, BLEModel.m文件拖入工程 ,在控制器中實現BluetoothConnection
方法就可以完成手機與外設連接,讀取外設數據,對外設發送指令的需求.