@[TOC](iOS 藍牙開發(fā) )
1.藍牙簡介
藍牙模式簡介
藍牙開發(fā)分為兩種模式,中心模式(central),和外設模式(peripheral)。一般來講涂召,我們需要在軟件內(nèi)連接硬件麻捻,通過連接硬件給硬件發(fā)送指令以完成一些動作的藍牙開發(fā)都是基于中心模式(central)模式的開發(fā),也就是說我們開發(fā)的app是中心兼蕊,我們要連接的硬件是外設初厚。如果需要其他設備連接手機藍牙,并對手機進行一些操作孙技,那就是基于外設模式(peripheral)的開發(fā)产禾。 本次我們主要介紹的就是中心模式的藍牙開發(fā)設備簡介
中心設備(CBCentralManager):iOS系統(tǒng)的手機等設備
外圍設備(CBPeripheral):手環(huán)等第三方設備藍牙數(shù)據(jù)傳輸簡介
將外圍設備(車輛)的數(shù)據(jù)傳送給中心設備(手機)時, 數(shù)據(jù)是經(jīng)過兩層包裝的
第一層是 Service(服務) , 可以是一個或多個, 比如車輛數(shù)據(jù)(服務)
第二層是 Characteristic(特征) , 他提供了更多關于Service(服務)的數(shù)據(jù), 例如車輛數(shù)據(jù)(服務)中包含了兩個數(shù)據(jù), 分別是里程數(shù)據(jù)和續(xù)航數(shù)據(jù), 這兩個就是車輛數(shù)據(jù)(服務)的具體數(shù)據(jù)(特征)具體操作簡介
讀(read) , 寫(write) , 訂閱(notify)
我們的目的是讀取設備中的數(shù)據(jù)(read) , 或者給設備寫入一定的數(shù)據(jù)(write)。有時候我們還想設備的數(shù)據(jù)變化的時候不需要我們手動去讀取這個值绪杏,需要設備自動通知我們它的值變化了下愈,值是多少。把值告訴app蕾久,這個時候就需要訂閱這個特征了(notify)其他相關概念
- 目前都使用的是低功耗藍牙4.0势似,藍牙外設必需為4.0及以上(2.0需要MFI認證),否則無法開發(fā),藍牙4.0設施由于低耗電履因,所以也叫做BLE障簿。
- CoreBluetooth框架的核心其實是兩個東西,peripheral和central, 能了解成外設和中心栅迄,就是你的蘋果手機就是中心站故,外部藍牙稱為外設。
- 服務和特征(service characteristic):簡而言之毅舆,外部藍牙中它有若干個服務service(服務你能了解為藍牙所擁有的可以力)西篓,而每個服務service下?lián)碛腥舾蓚€特征characteristic(特征你能了解為解釋這個服務的屬性)。
- Descriptor(形容)使用來形容characteristic變量的屬性憋活。例如岂津,一個descriptor能規(guī)定一個可讀的形容,或者者一個characteristic變量可接受的范圍悦即,或者者一個characteristic變量特定的單位吮成。
- 我們用的藍牙板塊是在淘寶買的, 大概十多元一個, ios大概每次能接受90個字節(jié), 安卓大概每次能接收20個字節(jié), 具體數(shù)字可可以會浮動, 應該是與藍牙板塊有關。
2. 藍牙連接
自己實踐的兩個藍牙demo:
- OC 編寫的:Bluetooth4_configWifi
- swifit編寫的:KYLBluetoothDemo_swift
2.1 CoreBluetooth框架
CoreBluetooth框架的核心其實是兩個東西辜梳,peripheral和central, 可以理解成外設和中心粱甫。對應他們分別有一組相關的API和類
- 這兩組api分別對應不同的業(yè)務場景,左側(cè)叫做中心模式作瞄,就是以你的app作為中心茶宵,連接其他的外設的場景,而右側(cè)稱為外設模式粉洼,使用手機作為外設別其他中心設備操作的場景节预。
- 服務和特征,特征的屬性(service and characteristic):
每個設備都會有一些服務属韧,每個服務里面都會有一些特征安拟,特征就是具體鍵值對,提供數(shù)據(jù)的地方宵喂。每個特征屬性分為這么幾種:讀糠赦,寫,通知這么幾種方式锅棕。
//objcetive c特征的定義枚舉
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
};
2.2 外設拙泽、服務、特征間的關系
2.3 藍牙連接過程
- 創(chuàng)建中心設備(CBCentralManager)
- 中心設備開始掃描(scanForPeripherals)
- 掃描到外圍設備之后, 自動調(diào)用中心設備的代理方法(didDiscoverPeripheral)
- 如果設備過多, 可以將掃描到的外圍設備添加到數(shù)組
- 開始連接, 從數(shù)組中過濾出自己想要的設備, 進行連接(connectPeripheral)
- 連接上之后, 自動調(diào)用中心設備的代理方法(didConnectPeripheral), 在代理中, 進行查找外圍設備的服務(peripheral.discoverServices)
- 查找到服務之后, 自動調(diào)用外圍設備的代理(didDiscoverServices), 可通過UUID,查找具體的服務,查找服務(discoverCharacteristics)
- 查找到特征之后, 自動調(diào)用外圍設備的代理(didDiscoverCharacteristics), 通過UUID找到自己想要的特征, 讀取特征(readValueForCharacteristic)
- 讀取到特征之后, 自動調(diào)用外設的代理方法(didUpdateValueForCharacteristic),在這里打印或者解析自己想要的特征值.
2.4 藍牙中心模式裸燎,外設模式
2.4.1 藍牙中心模式
- 中心模式流程
建立中心角色
掃描外設(discover)
連接外設(connect)
掃描外設中的服務和特征(discover)
4.1 獲取外設的services
4.2 獲取外設的Characteristics,獲取Characteristics的值顾瞻,獲取Characteristics的 Descriptor和Descriptor的值與外設做數(shù)據(jù)交互(explore and interact)
訂閱Characteristic的通知
斷開連接(disconnect)
2.4.2 藍牙外設模式
- 藍牙外設模式流程
啟動一個Peripheral管理對象
本地Peripheral設置服務,特性,描述,權(quán)限等等
Peripheral發(fā)送廣告
設置處理訂閱德绿、取消訂閱荷荤、讀characteristic退渗、寫characteristic的委托方法
2.5 藍牙設備狀態(tài)
- 藍牙設備狀態(tài)
待機狀態(tài)(standby):設備沒有傳輸和發(fā)送數(shù)據(jù),并且沒有連接到任何設
廣播狀態(tài)(Advertiser):周期性廣播狀態(tài)
掃描狀態(tài)(Scanner):主動尋找正在廣播的設備
發(fā)起鏈接狀態(tài)(Initiator):主動向掃描設備發(fā)起連接蕴纳。
主設備(Master):作為主設備連接到其他設備会油。
從設備(Slave):作為從設備連接到其他設備。
- 藍牙設備的五種工作狀態(tài)
準備(standby)
廣播(advertising)
監(jiān)聽掃描(Scanning
發(fā)起連接(Initiating)
已連接(Connected)
2.6 藍牙連接代碼實現(xiàn)
- 初始化
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
- 搜索掃描外圍設備
/**
* -- 初始化成功自動調(diào)用
* -- 必須實現(xiàn)的代理古毛,用來返回創(chuàng)建的centralManager的狀態(tài)翻翩。
* -- 注意:必須確認當前是CBCentralManagerStatePoweredOn狀態(tài)才可以調(diào)用掃描外設的方法:
scanForPeripheralsWithServices
*/
- (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");
// 開始掃描周圍的外設。
/*
-- 兩個參數(shù)為Nil表示默認掃描所有可見藍牙設備稻薇。
-- 注意:第一個參數(shù)是用來掃描有指定服務的外設嫂冻。然后有些外設的服務是相同的,比如都有FFF5服務塞椎,那么都會發(fā)現(xiàn)絮吵;而有些外設的服務是不可見的,就會掃描不到設備忱屑。
-- 成功掃描到外設后調(diào)用didDiscoverPeripheral
*/
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
break;
default:
break;
}
}
#pragma mark 發(fā)現(xiàn)外設
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary*)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"Find device:%@", [peripheral name]);
if (![_deviceDic objectForKey:[peripheral name]]) {
NSLog(@"Find device:%@", [peripheral name]);
if (peripheral!=nil) {
if ([peripheral name]!=nil) {
if ([[peripheral name] hasPrefix:@"根據(jù)設備名過濾"]) {
[_deviceDic setObject:peripheral forKey:[peripheral name]];
// 停止掃描, 看需求決定要不要加
// [_centralManager stopScan];
// 將設備信息傳到外面的頁面(VC), 構(gòu)成掃描到的設備列表
if ([self.delegate respondsToSelector:@selector(dataWithBluetoothDic:)]) {
[self.delegate dataWithBluetoothDic:_deviceDic];
}
}
}
}
}
}
- 連接外圍設備
// 連接設備(.h中聲明出去的接口, 一般在點擊設備列表連接時調(diào)用)
- (void)connectDeviceWithPeripheral:(CBPeripheral *)peripheral
{
[self.centralManager connectPeripheral:peripheral options:nil];
}
#pragma mark 連接外設--成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
//連接成功后停止掃描,節(jié)省內(nèi)存
[central stopScan];
peripheral.delegate = self;
self.peripheral = peripheral;
//4.掃描外設的服務
/**
-- 外設的服務暇昂、特征莺戒、描述等方法是CBPeripheralDelegate的內(nèi)容,所以要先設置代理peripheral.delegate = self
-- 參數(shù)表示你關心的服務的UUID急波,比如我關心的是"FFE0",參數(shù)就可以為@[[CBUUID UUIDWithString:@"FFE0"]].那么didDiscoverServices方法回調(diào)內(nèi)容就只有這兩個UUID的服務从铲,不會有其他多余的內(nèi)容,提高效率澄暮。nil表示掃描所有服務
-- 成功發(fā)現(xiàn)服務名段,回調(diào)didDiscoverServices
*/
[peripheral discoverServices:@[[CBUUID UUIDWithString:@"你要用的服務UUID"]]];
if ([self.delegate respondsToSelector:@selector(didConnectBle)]) {
// 已經(jīng)連接
[self.delegate didConnectBle];
}
}
#pragma mark 連接外設——失敗
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"%@", error);
}
#pragma mark 取消與外設的連接回調(diào)
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"%@", peripheral);
}
- 獲得外圍設備的服務
#pragma mark 發(fā)現(xiàn)服務回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
//NSLog(@"didDiscoverServices,Error:%@",error);
CBService * __nullable findService = nil;
// 遍歷服務
for (CBService *service in peripheral.services)
{
//NSLog(@"UUID:%@",service.UUID);
if ([[service UUID] isEqual:[CBUUID UUIDWithString:@"你要用的服務UUID"]])
{
findService = service;
}
}
NSLog(@"Find Service:%@",findService);
if (findService)
[peripheral discoverCharacteristics:NULL forService:findService];
}
#pragma mark 發(fā)現(xiàn)特征回調(diào)
/**
-- 發(fā)現(xiàn)特征后,可以根據(jù)特征的properties進行:讀readValueForCharacteristic泣懊、寫writeValue伸辟、訂閱通知setNotifyValue、掃描特征的描述discoverDescriptorsForCharacteristic馍刮。
**/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"你要用的特征UUID"]]) {
/**
-- 讀取成功回調(diào)didUpdateValueForCharacteristic
*/
self.characteristic = characteristic;
// 接收一次(是讀一次信息還是數(shù)據(jù)經(jīng)常變實時接收視情況而定, 再決定使用哪個)
// [peripheral readValueForCharacteristic:characteristic];
// 訂閱, 實時接收
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
// 發(fā)送下行指令(發(fā)送一條)
NSData *data = [@"硬件工程師給我的指令, 發(fā)送給藍牙該指令, 藍牙會給我返回一條數(shù)據(jù)" dataUsingEncoding:NSUTF8StringEncoding];
// 將指令寫入藍牙
[self.peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
/**
-- 當發(fā)現(xiàn)characteristic有descriptor,回調(diào)didDiscoverDescriptorsForCharacteristic
*/
[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}
- 從外圍設備讀取數(shù)據(jù)
#pragma mark - 獲取值
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
// characteristic.value就是藍牙給我們的值(我這里是json格式字符串)
NSData *jsonData = [characteristic.value dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dataDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
// 將字典傳出去就可以使用了
}
#pragma mark - 中心讀取外設實時數(shù)據(jù)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (characteristic.isNotifying) {
[peripheral readValueForCharacteristic:characteristic];
} else {
NSLog(@"Notification stopped on %@. Disconnecting", characteristic);
NSLog(@"%@", characteristic);
[self.centralManager cancelPeripheralConnection:peripheral];
}
}
- 給外圍設備發(fā)送(寫入)數(shù)據(jù)
// 上文中發(fā)現(xiàn)特征之后, 發(fā)送下行指令的時候其實就是向藍牙中寫入數(shù)據(jù)
// 例:
// 發(fā)送檢查藍牙命令
- (void)writeCheckBleWithBle
{
_style = 1;
// 發(fā)送下行指令(發(fā)送一條)
NSData *data = [@"硬件工程師提供給你的指令, 類似于5E16010203...這種很長一串" dataUsingEncoding:NSUTF8StringEncoding];
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}
#pragma mark 數(shù)據(jù)寫入成功回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSLog(@"寫入成功");
if ([self.delegate respondsToSelector:@selector(didWriteSucessWithStyle:)]) {
[self.delegate didWriteSucessWithStyle:_style];
}
}
- 停止掃描
#pragma mark 停止掃描外設
- (void)stopScanPeripheral{
[self.centralManager stopScan];
}
#pragma mark 掃描外設
- (void)scanDevice
{
if (_centralManager == nil) {
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[_deviceDic removeAllObjects];
}
}
- 斷開連接
#pragma mark 斷開連接
- (void)disConnectPeripheral{
/**
-- 斷開連接后回調(diào)didDisconnectPeripheral
-- 注意斷開后如果要重新掃描這個外設信夫,需要重新調(diào)用[self.centralManager scanForPeripheralsWithServices:nil options:nil];
*/
[self.centralManager cancelPeripheralConnection:self.peripheral];
}