最近做的是藍(lán)牙的項(xiàng)目在開發(fā)過程中有了很多或深或淺的認(rèn)識(shí)京革,這里分享給大家绳泉,共勉冠跷!
這里的基礎(chǔ)篇主要講一下最基礎(chǔ)的iOS藍(lán)牙開發(fā)合是。
藍(lán)牙連接可以大致分為以下幾個(gè)步驟
1.建立一個(gè)Central Manager實(shí)例進(jìn)行藍(lán)牙管理
2.搜索外圍設(shè)備
3.連接外圍設(shè)備
4.獲得外圍設(shè)備的服務(wù)
5.獲得服務(wù)的特征
6.從外圍設(shè)備讀數(shù)據(jù)
7.給外圍設(shè)備發(fā)送數(shù)據(jù)
第一步:建立中心管理者進(jìn)行藍(lán)牙管理
在使用藍(lán)牙的地方導(dǎo)入#import <CoreBluetooth/CoreBluetooth.h>
并簽訂協(xié)議CBCentralManagerDelegate,CBPeripheralDelegate
- (void)useBlueTooth
{
//初始化
//CBCentralManager是藍(lán)牙中心的管理類衫樊,控制著藍(lán)牙的掃描飒赃,連接,藍(lán)牙狀態(tài)的改變科侈。
self.centralManager = [[CBCentralManager alloc]initWithDelegate:self queue:nil options:nil];
//掃描設(shè)備
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
#pragma mark CBCentralManagerDelegate
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
CBManagerState state = central.state;
NSString *stateString = nil;
switch(state)
{
case CBManagerStateResetting:
stateString = @"CBManagerStateResetting";
break;
case CBManagerStateUnsupported:
stateString = @"CBManagerStateUnsupported";
break;
case CBManagerStateUnauthorized:
stateString = @"CBManagerStateUnauthorized";
break;
case CBManagerStatePoweredOff:
stateString = @"CBManagerStatePoweredOff";
break;
case CBManagerStatePoweredOn:
stateString = @"CBManagerStatePoweredOn";
break;
case CBManagerStateUnknown:
default:
stateString = @"CBManagerStateUnknown";
}
NSLog(@"藍(lán)牙狀態(tài):%@",stateString);
}
打印結(jié)果:
what载佳?
[CoreBluetooth] API MISUSE: <CBCentralManager: 0x170274b80> can only accept this command while in the powered on state
查閱資料:https://stackoverflow.com/questions/23338767/ios-core-bluetooth-getting-api-misuse-warning
只有在確定藍(lán)牙打開的情況下,才可以調(diào)用掃描的方法
那下面那就又是什么鬼臀栈?
[CoreBluetooth] XPC connection invalid
查閱資料:http://www.reibang.com/p/ec659ffcacfe
發(fā)現(xiàn)創(chuàng)建出的CBCentralManager實(shí)例必須被VC所持有蔫慧,如果是封裝出來的類,該實(shí)例也必須被VC所持有权薯,
使用時(shí):
從打印結(jié)果可以看出現(xiàn)在可以了
第二步:搜索外設(shè)
根據(jù)上面的試驗(yàn)姑躲,要在確認(rèn)藍(lán)牙連接的情況下,掃描并打印外設(shè)
#pragma mark 掃描外設(shè)
- (void)scan
{
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
//掃描到設(shè)備會(huì)調(diào)用
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(@"peripheral:%@",peripheral);
}
這里掃描外設(shè)會(huì)看到這種各樣的藍(lán)牙外設(shè)盟蚣,還會(huì)有自己的筆記本電腦
注意:藍(lán)牙連接過的設(shè)備掃描不上黍析,需要在設(shè)置—藍(lán)牙—忽略該設(shè)備
第三步:藍(lán)牙連接
掃描到設(shè)備后,如果有目標(biāo)設(shè)備屎开,就需要藍(lán)牙連接該設(shè)備阐枣,但是掃描到的設(shè)備很多很多,而且會(huì)有重復(fù)的奄抽,這里可以通過設(shè)備的名字來匹配是否是目標(biāo)設(shè)備蔼两,然后選擇是否連接。一般APP中會(huì)讓用戶自己選擇連接哪個(gè)手環(huán)如孝。
#pragma mark CBCentralManagerDelegate
//連接成功的回調(diào)
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"連接成功peripheral:%@",peripheral);
//連接成功之后尋找服務(wù)宪哩,傳nil會(huì)尋找所有服務(wù)
[peripheral discoverServices:nil];
self.peripheral = peripheral;
peripheral.delegate = self;
}
//連接失敗的回調(diào)
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"連接失敗peripheral:%@",peripheral);
}
//斷開連接的回調(diào)
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"斷開連接peripheral:%@",peripheral);
}
//掃描到設(shè)備會(huì)調(diào)用
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI
{
// NSLog(@"peripheral:%@",peripheral);
//手環(huán)測(cè)試
if ([peripheral.name isEqualToString:@"NAME"]) {
NSLog(@"掃描到peripheral:%@,advertisementData:%@",peripheral,advertisementData);
//發(fā)起連接
[self.centralManager connectPeripheral:peripheral options:nil];
//必須引用要不會(huì)報(bào)錯(cuò)
self.peripheral = peripheral;
}
}
連接狀態(tài)的回調(diào)也可以清楚的看到,連接成功的話第晰,我們就可以搜索外設(shè)的服務(wù)锁孟;連接失敗會(huì)回調(diào)彬祖,可以打印查看為何失敗。
第四步:獲得藍(lán)牙的服務(wù)
連接成功后品抽,可以獲得藍(lán)牙的服務(wù)
藍(lán)牙的各個(gè)服務(wù),可以理解為藍(lán)牙提供的數(shù)據(jù)分類储笑,特征是具體的各個(gè)數(shù)據(jù)
#pragma mark CBCentralManagerDelegate
//連接成功的回調(diào)
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"連接成功peripheral:%@",peripheral);
//連接成功之后尋找服務(wù),傳nil會(huì)尋找所有服務(wù)
[peripheral discoverServices:nil];
self.peripheral = peripheral;
peripheral.delegate = self;
}
//發(fā)現(xiàn)服務(wù)的回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
NSLog(@"self.peripheral.services:%@",peripheral.services);
NSLog(@"error:%@",error);
}
這里具體的服務(wù)定義要看藍(lán)牙協(xié)議圆恤,其中的UUID是唯一識(shí)別服務(wù)的突倍。
第五步:獲得藍(lán)牙的特征
發(fā)現(xiàn)服務(wù)后我們可以搜索服務(wù)下的特征,一般一個(gè)服務(wù)下都包含多個(gè)特征
//發(fā)現(xiàn)服務(wù)的回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
NSLog(@"self.peripheral.services:%@",peripheral.services);
NSLog(@"error:%@",error);
if (!error) {
for (CBService *service in peripheral.services) {
// NSLog(@"發(fā)現(xiàn)服務(wù)serviceUUID:%@", service.UUID.UUIDString);
//發(fā)現(xiàn)特定服務(wù)的特征值
[service.peripheral discoverCharacteristics:nil forService:service];
}
}
}
//發(fā)現(xiàn)特征回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"發(fā)現(xiàn)特征:%@",characteristic);
//訂閱
[self.peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
特征也包含唯一識(shí)別的UUID
第六步:獲得藍(lán)牙數(shù)據(jù)盆昙,解析藍(lán)牙數(shù)據(jù)
無論是read還是notify都是在這個(gè)方法獲得數(shù)據(jù)
//數(shù)據(jù)接收的回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
//獲取訂閱特征回復(fù)的數(shù)據(jù)
NSData *data = characteristic.value;
//獲得數(shù)據(jù)的字節(jié)長(zhǎng)度
NSUInteger dataLength = data.length;
NSLog(@"dataLength:%lu",dataLength);
NSLog(@"UDID:%@,data:%@",characteristic.UUID,data);
}
這里我們已經(jīng)獲得了藍(lán)牙的部分?jǐn)?shù)據(jù)羽历,但是這一串的是個(gè)什么鬼?原來藍(lán)牙傳過來的數(shù)據(jù)并不是我們平時(shí)的10進(jìn)制數(shù)據(jù)淡喜,是16進(jìn)制的數(shù)據(jù)秕磷,我們需要根據(jù)藍(lán)牙協(xié)議進(jìn)行解析,如果沒有協(xié)議炼团,那就看代碼吧澎嚣!跟我一樣,哈哈瘟芝!
比如分段計(jì)步的藍(lán)牙數(shù)據(jù)是這樣的易桃。每2位數(shù)代表一個(gè)字節(jié)的16進(jìn)制數(shù)據(jù),藍(lán)牙協(xié)議規(guī)定分段計(jì)步是14個(gè)字節(jié)锌俱,這里總共是28位數(shù)晤郑。
第七步:藍(lán)牙數(shù)據(jù)的寫入
這里我們?cè)囈幌吕锍獭崃渴欠耧@示的設(shè)置的寫
數(shù)據(jù)的寫入也是要看協(xié)議的嚼鹉,如果沒有協(xié)議贩汉,看代碼吧。
//發(fā)現(xiàn)特征回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"發(fā)現(xiàn)特征:%@",characteristic);
//訂閱
[self.peripheral setNotifyValue:YES forCharacteristic:characteristic];
![![![![![![屏幕快照 2017-10-13 上午10.01.12.png](http://upload-images.jianshu.io/upload_images/2519635-7d6d063c49542973.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](http://upload-images.jianshu.io/upload_images/2519635-379e65d2cc1e2f9f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](http://upload-images.jianshu.io/upload_images/2519635-cb9c524dfd1ddb53.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](http://upload-images.jianshu.io/upload_images/2519635-1a3d5be07e58b248.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](http://upload-images.jianshu.io/upload_images/2519635-de9772803e8d6909.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
](http://upload-images.jianshu.io/upload_images/2519635-115bade4a14ca3ee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
NSString *settingString = [NSString stringWithFormat:@"4|%d|%d|%d|0000000", 0, 0,0];
NSData *sendData = [settingString dataUsingEncoding:NSUTF8StringEncoding];
//該設(shè)置的服務(wù)UUID
CBUUID *timeSyncServiceUUID = [CBUUID UUIDWithString:@"1820"];
//該特征的UUID
CBUUID *timeSyncCharacteristicUUID = [CBUUID UUIDWithString:@"6e400003-b5a3-f393-e0a9-e50e24dcca9e"];
for(CBService *service in self.peripheral.services)
{
// NSLog(@"self.peripheral.services:%@",service);
if ([service.UUID isEqual: timeSyncServiceUUID]) {
for(CBCharacteristic *characteristic in service.characteristics)
{
if([characteristic.UUID isEqual:timeSyncCharacteristicUUID])
{
// NSLog(@"sendData:%@",sendData);
//寫入數(shù)據(jù)
[peripheral writeValue:sendData forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
}
}
}
}
}
//是否寫入成功的代理
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"===寫入錯(cuò)誤:%@",error);
}else{
NSLog(@"===寫入成功%@",characteristic);
}
}
藍(lán)牙授權(quán)的問題
根據(jù)蘋果文檔中所說的锚赤,APP沒有藍(lán)牙授權(quán)是無法訪問外設(shè)數(shù)據(jù)匹舞,然而我發(fā)現(xiàn)我們有授權(quán)APP,APP依然可以訪問外設(shè)的數(shù)據(jù)线脚,為什么赐稽?
感謝以下博客的作者的分享!
參考資料
基礎(chǔ):http://www.cocoachina.com/ios/20150915/13454.html
實(shí)現(xiàn):http://www.reibang.com/p/f7a53b3a0fc8
解析:http://www.reibang.com/p/1f41e6fe06bf
http://www.reibang.com/p/1b3c8fc6995a
蘋果:https://developer.apple.com/bluetooth/
https://developer.apple.com/hardwaredrivers/BluetoothDesignGuidelines.pdf
https://learn.adafruit.com/introduction-to-bluetooth-low-energy?view=all
https://race604.com/gatt-profile-intro/