首先進一則廣告:
藍牙技術聯盟(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也是可以的,做人呢最重要是開心。。。哈哈哈哈