本文介紹了藍牙的概念以及具體的使用步驟.
一.藍牙概念
藍牙2.0為傳統藍牙,傳統藍牙也稱為經典藍牙.
藍牙4.0因為低耗電,所以也叫做低功耗藍(BLE).它將三種規格集一體,包括傳統藍牙技術、高速技術和低耗能技術.
二.BLE支持兩種部署方式
- 雙模式
低功耗藍牙功能集成在現有的經典藍牙控制器中,或在現有經典藍牙技術芯片上增加低功耗堆棧
,整體架構基本不變,因此成本增加有限. - 單模式
面向高度集成、緊湊的設備,使用一個輕量級連接層(Link Layer)提供超低功耗的待機模式操作、簡單設備恢復和可靠的點對多點數據傳輸,還能讓聯網傳感器在藍牙傳輸中安排好低功耗藍牙流量的次序,同時還有高級節能和安全加密連接.
三.藍牙各版本使用選擇
- 藍牙2.0,不上架
使用私有API,手機需要越獄. - 藍牙2.0,要上架
進行MFI認證,使用ExternalAccessory框架.手機不需要越獄. - 藍牙4.0,要上架
使用CoreBluetooth框架,手機不需要越獄.(CoreBluetooth是基于BLE來開發的) - 說明
對于小的硬件廠商來說,MFI認證通過幾率不大,不僅耗錢還耗時,所以,還是推薦使用藍牙4.0.
(MFI:Make for ipad ,iphone, itouch 專們為蘋果設備制作的設備)
四.問題描述
公司要求iOS端需要和鋼琴進行藍牙連接并進行數據通信,我以為鋼琴是藍牙4.0,然后快速集成CoreBluetooth框架寫了一個demo,掃描外設時,沒有發現鋼琴的藍牙名稱,可是用iphone打開系統設置,可以發現鋼琴對應的藍牙.問了安卓的同事,得知鋼琴的藍牙只有2.0的模塊,所以,安卓端是用2.0藍牙進行交互的.公司決定不做MFI認證,改用藍牙4.0.在與硬件廠商交涉的過程中,得知鋼琴中的藍牙是4.0的,但是,他們在設計藍牙板子的時候,沒有集成低功耗技術.之后,板子寄回硬件廠商,添加BLE模塊.這才踏上藍牙4.0的正軌.
五.藍牙4.0使用解析
1.基本知識
central:中心,連接硬件的設備.
peripheral:外設,被連接的硬件.
說明:外設在一直廣播,當你創建的中心對象在掃描外設時,就能夠發現外設.
如圖所示:
service:服務.
characteristic:特征.
說明:一個外設包含多個服務,而每一個服務中又包含多個特征,特征包括特征的值和特征的描述.每個服務包含多個字段,字段的權限有read(讀)、write(寫)、notify(通知).
2.藍牙4.0分為兩種模式
- 中心模式流程
- 建立中心角色
[[CBCentralManager alloc] initWithDelegate:self queue:nil]
- 掃描外設
scanForPeripherals
- 發現外設
didDiscoverPeripheral
- 連接外設
connectPeripheral
4.1 連接失敗didFailToConnectPeripheral
4.2 連接斷開didDisconnectPeripheral
4.3 連接成功didConnectPeripheral
- 掃描外設中的服務
discoverServices
5.1 發現并獲取外設中的服務didDiscoverServices
- 掃描外設對應服務的特征
discoverCharacteristics
6.1 發現并獲取外設對應服務的特征didDiscoverCharacteristicsForService
6.2 給對應特征寫數據writeValue:forCharacteristic:type:
- 訂閱特征的通知
setNotifyValue:forCharacteristic:
7.1 根據特征讀取數據didUpdateValueForCharacteristic
- 建立中心角色
- 外設模式流程
- 建立外設角色
- 設置本地外設的服務和特征
- 發布外設和特征
- 廣播服務
- 響應中心的讀寫請求
- 發送更新的特征值,訂閱中心
六.藍牙4.0開發步驟
1.本文采用中心模式
導入CoreBluetooth框架,#import <CoreBluetooth/CoreBluetooth.h>
2.遵守CBCentralManagerDelegate,CBPeripheralDelegate
協議
3.添加屬性
// 中心管理者(管理設備的掃描和連接)
@property (nonatomic, strong) CBCentralManager *centralManager;
// 存儲的設備
@property (nonatomic, strong) NSMutableArray *peripherals;
// 掃描到的設備
@property (nonatomic, strong) CBPeripheral *cbPeripheral;
// 文本
@property (weak, nonatomic) IBOutlet UITextView *peripheralText;
// 外設狀態
@property (nonatomic, assign) CBManagerState peripheralState;
常量,具體服務和特征是讀還是寫的類型,問公司硬件廠商,或者問同事.
// 藍牙4.0設備名
static NSString * const kBlePeripheralName = @"公司硬件藍牙名稱";
// 通知服務
static NSString * const kNotifyServerUUID = @"FFE0";
// 寫服務
static NSString * const kWriteServerUUID = @"FFE1";
// 通知特征值
static NSString * const kNotifyCharacteristicUUID = @"FFE2";
// 寫特征值
static NSString * const kWriteCharacteristicUUID = @"FFE3";
4.創建中心管理者
- (CBCentralManager *)centralManager
{
if (!_centralManager)
{
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
return _centralManager;
}
創建存儲設備數組
- (NSMutableArray *)peripherals
{
if (!_peripherals) {
_peripherals = [NSMutableArray array];
}
return _peripherals;
}
5.掃描設備之前會調用中心管理者狀態改變的方法
// 當狀態更新時調用(如果不實現會崩潰)
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state) {
case CBManagerStateUnknown:{
NSLog(@"未知狀態");
self.peripheralState = central.state;
}
break;
case CBManagerStateResetting:
{
NSLog(@"重置狀態");
self.peripheralState = central.state;
}
break;
case CBManagerStateUnsupported:
{
NSLog(@"不支持的狀態");
self.peripheralState = central.state;
}
break;
case CBManagerStateUnauthorized:
{
NSLog(@"未授權的狀態");
self.peripheralState = central.state;
}
break;
case CBManagerStatePoweredOff:
{
NSLog(@"關閉狀態");
self.peripheralState = central.state;
}
break;
case CBManagerStatePoweredOn:
{
NSLog(@"開啟狀態-可用狀態");
self.peripheralState = central.state;
}
break;
default:
break;
}
}
掃描設備
// 掃描設備
- (IBAction)scanForPeripherals
{
[self.centralManager stopScan];
NSLog(@"掃描設備");
[self showMessage:@"掃描設備"];
if (self.peripheralState == CBManagerStatePoweredOn)
{
// 掃描所有設備,傳入nil,代表所有設備.
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
}
6.掃描到設備并開始連接
/**
掃描到設備
@param central 中心管理者
@param peripheral 掃描到的設備
@param advertisementData 廣告信息
@param RSSI 信號強度
*/
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI
{
[self showMessage:[NSString stringWithFormat:@"發現設備,設備名:%@",peripheral.name]];
if (![self.peripherals containsObject:peripheral])
{
[self.peripherals addObject:peripheral];
NSLog(@"%@",peripheral);
if ([peripheral.name isEqualToString:kBlePeripheralName])
{
[self showMessage:[NSString stringWithFormat:@"設備名:%@",peripheral.name]];
self.cbPeripheral = peripheral;
[self showMessage:@"開始連接"];
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
}
7.連接的三種狀態,如果連接成功,則掃描所有服務(也可以掃描指定服務)
連接失敗重連
/**
連接失敗
@param central 中心管理者
@param peripheral 連接失敗的設備
@param error 錯誤信息
*/
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
[self showMessage:@"連接失敗"];
if ([peripheral.name isEqualToString:kBlePeripheralName])
{
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
連接斷開重連
/**
連接斷開
@param central 中心管理者
@param peripheral 連接斷開的設備
@param error 錯誤信息
*/
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
[self showMessage:@"斷開連接"];
if ([peripheral.name isEqualToString:kBlePeripheralName])
{
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
連接成功并掃描服務
/**
連接成功
@param central 中心管理者
@param peripheral 連接成功的設備
*/
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"連接設備:%@成功",peripheral.name);
[self showMessage:[NSString stringWithFormat:@"連接設備:%@成功",peripheral.name]];
// 設置設備的代理
peripheral.delegate = self;
// services:傳入nil代表掃描所有服務
[peripheral discoverServices:nil];
}
8.發現服務并掃描服務對應的特征
/**
掃描到服務
@param peripheral 服務對應的設備
@param error 掃描錯誤信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
// 遍歷所有的服務
for (CBService *service in peripheral.services)
{
NSLog(@"服務:%@",service.UUID.UUIDString);
// 獲取對應的服務
if ([service.UUID.UUIDString isEqualToString:kWriteServerUUID] || [service.UUID.UUIDString isEqualToString:kNotifyServerUUID])
{
// 根據服務去掃描特征
[peripheral discoverCharacteristics:nil forService:service];
}
}
}
9.掃描到對應的特征,寫入特征的值,并訂閱指定的特征通知.
/**
掃描到對應的特征
@param peripheral 設備
@param service 特征對應的服務
@param error 錯誤信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
// 遍歷所有的特征
for (CBCharacteristic *characteristic in service.characteristics)
{
NSLog(@"特征值:%@",characteristic.UUID.UUIDString);
// 獲取對應的特征
if ([characteristic.UUID.UUIDString isEqualToString:kWriteCharacteristicUUID])
{
// 寫入數據
[self showMessage:@"寫入特征值"];
for (Byte i = 0x0; i < 0x73; i++)
{
// 讓鋼琴的每顆燈都亮一次
Byte byte[] = {0xf0, 0x3d, 0x3d, i,
0x02,0xf7};
NSData *data = [NSData dataWithBytes:byte length:6];
[peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
}
if ([characteristic.UUID.UUIDString isEqualToString:kNotifyCharacteristicUUID])
{
// 訂閱特征通知
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
}
10.根據特征讀取到數據
/**
根據特征讀到數據
@param peripheral 讀取到數據對應的設備
@param characteristic 特征
@param error 錯誤信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error
{
if ([characteristic.UUID.UUIDString isEqualToString:kNotifyCharacteristicUUID])
{
NSData *data = characteristic.value;
NSLog(@"%@",data);
}
}
讀取值打印結果:
2017-04-25 12:34:41.876974+0800 藍牙4.0Demo[1745:346611] <9f5436>
2017-04-25 12:34:41.983016+0800 藍牙4.0Demo[1745:346611] <8f5440>
2017-04-25 12:34:42.154821+0800 藍牙4.0Demo[1745:346611] <9f5649>
2017-04-25 12:34:42.239481+0800 藍牙4.0Demo[1745:346611] <8f5640>
提示:上Appstore下載LightBlue,進行藍牙通信測試.