跟L大帝一起做~iOS藍牙開發

首先進一則廣告:

藍牙技術聯盟(Bluetooth SIG)2010年7月7日宣布,正式采納藍牙4.0核心規范(Bluetooth Core Specification Version 4.0 ),并啟動對應的認證計劃。會員廠商可以提交其產品進行測試,通過后將獲得藍牙4.0標準認證。 該技術擁有極低的運行和待機功耗,使用一粒紐扣電池甚至可連續工作數年之久。所以藍牙技術還是可以被長久利用的一種鏈接技術。

然后直接進入正題。

大家也許會注意到一個問題。安卓和安卓手機可以用藍牙相互鏈接,但是蘋果和蘋果手機就不行。因為蘋果鏈接用的是airdrop,所以也就不需要藍牙傳輸大文件。大家也就忽略了這個現象。其實airdrop本質也是藍牙建立鏈接的。。。。。。。

今天主要就是用一款app去讀寫藍牙設備。應用場景就是手機掃描外設,鏈接外設,找到外設的服務和屬性,對服務和屬性進行讀寫操作;

代碼實現流程。

1.建立中心管理控制對象


```objc

#import <CoreBluetooth/CoreBluetooth.h>(導入頭文件)

<CBCentralManagerDelegate,CBPeripheralDelegate>繼承代理協議

//系統藍牙設備中心管理對象可以去掃描和鏈接外設

CBCentralManager *_manager;

//用于保存被發現設備

NSMutableArray *_discoverPeripherals;

//初始化并設置委托和線程隊列,最好一個線程的參數可以為nil,默認會就main線程

manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];

//創建外設數組,保存發現的設備。不保存設備會導致代理方法不可用

discoverPeripherals = [[NSMutableArray array];

```


2.掃描外設,只有設備打開狀態,才可以掃描。(錯誤原因之一)


介紹一下

CBCentralManagerDelegate的主要代理方法。

必須實現的:

- (void)centralManagerDidUpdateState:(CBCentralManager *)central;//主設備狀態改變的委托,在初始化CBCentralManager的時候會打開設備,只有當設備正確打開后才能使用

其他選擇實現的代理方法。

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; //找到外設的委托

- (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;//斷開外設的委托



//工程中的代碼不適合直接放出來,就用示例demo中的了。(感謝這些偉大的開發工作者)

這些狀態不需要我一一翻譯了吧,如果不懂可以私信我。我一定讓你自己查字典去。。。

-(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;

*/

[central scanForPeripheralsWithServices:nil options:nil];

break;

default:

break;

}

}


3.掃描完成,進入連接方法進行設備連接。


-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{

NSLog(@"當掃描到設備:%@",peripheral.name);

//這里自己去設置下連接規則,這里就是不同廠商特定設備鏈接的判斷規則。L是我喜歡的字母。

//? ? if ([peripheral.name hasPrefix:@"L"]){

/*

一個主設備最多能連7個外設,每個外設最多只能給一個主設備連接,連接成功,失敗,斷開會進入各自的委托

//這個沒有驗證過。沒有那么多測試設備。。。。

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設成功的委托

- (void)centra`lManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設連接失敗的委托

- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開外設的委托

*/

//找到的設備必須持有它,否則CBCentralManager中也不會保存peripheral,那么CBPeripheralDelegate中的方法也不會被調用!!(錯誤之一)

[discoverPeripherals addObject:peripheral];

[central connectPeripheral:peripheral options:nil];

//? ? }

}

//連接到Peripherals-成功(其他操作不是我們想要的,自己做相關處理)

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{? ? NSLog(@">>>連接到名稱為(%@)的設備-成功",peripheral.name);? ? //設置的peripheral委托CBPeripheralDelegate? ? //@interface ViewController : UIViewController[peripheral setDelegate:self];

//掃描外設Services,成功后會進入方法:-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{

[peripheral discoverServices:nil];

}


4.接下來是發現設備的服務,對相關服務中的特性進行操作。


//掃描到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

-(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);

}


5.解析到這些數據,就可以滿足日常計步,心率等一系列只讀數據的操作。接下來是向外設寫數據。


//寫數據

-(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(@"該字段不可寫!");

}

}

//訂閱外設特性的通知。

//設置通知,數據通知會進入:didUpdateValueForCharacteristic方法

[peripheral setNotifyValue:YES forCharacteristic:characteristic];

//取消通知

[peripheral setNotifyValue:NO forCharacteristic:characteristic];

//設置通知和取消通知均可以觸發此方法。

- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error;


6.操作完成 或者電量不足要進行停止掃描斷開設備操作。


//停止掃描并斷開連接

-(void)disconnectPeripheral:(CBCentralManager *)centralManager

peripheral:(CBPeripheral *)peripheral{

//停止掃描

[centralManager stopScan];

//斷開連接

[centralManager cancelPeripheralConnection:peripheral];

}


本來藍牙開發就需要硬件工程師定義好每個硬件對應的服務,每個服務的描述以及服務特性的屬性。所以開發中的代碼一般都已經定義好了。最重要的就是了解各種方法是什么作用。在各自開發的過程中是否必須用到。為了讓大家有個跟更加進一步的了解。接下來我再說一下服務特性的具體讀寫與訂閱。當然我也是搜索了很多資料。借鑒了很多大神的資源。當時在github上下載了好多東西。具體是哪位大神寫的也不曉得。反正很崇拜這些人。。。O(∩_∩)O哈哈哈~

我們看一下外設管理類

以上寫的是centralManager的內容接下來是peripheralManager。

1.初始化外設管理者


//外設管理者

CBPeripheralManager *_peripheralManager;

//首先初始化外設管理者

peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:self queue:nil];(默認為主隊列)

//在你用過之后記得停止廣播

//[peripheralManager stopAdvertising];


2.配置藍牙讀寫的屬性以及描述


//注意此方法是在保證外設在CBPeripheralManagerStatePoweredOn狀態下才可調用***********

-(void)initService{

//characteristics字段描述

CBUUID *CBUUIDCharacteristicUserDescriptionStringUUID = [CBUUID UUIDWithString:CBUUIDCharacteristicUserDescriptionString];

/*

可以給主設備發送通知的特性?

properties:CBCharacteristicPropertyNotify

許可特性可讀

permissions CBAttributePermissionsReadable

*/

CBMutableCharacteristic *notiyCharacteristic = [[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:notiyCharacteristicUUID] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

/*

可讀寫的特性

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];

//serviceOne初始化并加入兩個characteristics

CBMutableService *serviceOne = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID1] primary:YES];

NSLog(@"%@",serviceOne.UUID);

[serviceOne setCharacteristics:@[notiyCharacteristic,readwriteCharacteristic]];

//serviceTwo初始化并加入一個characteristics

CBMutableService *serviceTwo = [[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:ServiceUUID2] primary:YES];

[serviceTwo setCharacteristics:@[readCharacteristic]];

//添加后就會調用代理的

- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error

[peripheralManager addService:serviceOne];

[peripheralManager addService:serviceTwo];

}


3.外設管理者相關的代理方法介紹


//perihpheral添加了service就調用此方法

- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{

if (!error) {

//兩次都添加完成后才去發送廣播

//添加服務后可以在此向外界發出通告 調用完這個方法后會調用代理的

//-(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{

//在這里做一些發送廣播時需要的記錄或者動作。

}

//This method is invoked when a central configurescharacteristicto notify or indicate. *? ? ? ? ? ? ? ? ? ? ? ? ? It should be used as a cue to start sending updates as the characteristic value changes.

//當特性的值發生改變就會調用這個方法

//訂閱characteristics

-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{

NSLog(@"訂閱了 %@的數據",characteristic.UUID);

//每秒執行一次給主設備發送一個當前時間的秒數(這里就是最主要的發送數據的方法)

timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(sendDate:) userInfo:characteristic? repeats:YES];

}

//發送數據,發送當前時間的秒數(這里自己定制)

-(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];

}

}


至此藍牙的整個連接發送數據的過程也就完成了。蘋果的文檔中也介紹的很清楚。如果你覺得某種情況需要操作但是本文沒有寫到的。可以看看蘋果的文檔。也許你會發現更多利用起來很方便的方法。

學習新知識最好站在巨人的肩膀上。這樣可以少走彎路。感謝那些大公無私的為大家提供學習知識的大牛。你覺得挺不錯可以給個喜歡喲。動動手指關注我也是可以的,做個好基友也是可以的,玩個lol也是可以的,做人呢最重要是開心。。。哈哈哈哈

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容