藍牙3.0協議由于高速度,通常用于音視頻播放
藍牙4.0協議以低功耗著稱,傳輸速度慢,一般用于控制藍牙設備
這里介紹iOS的CoreBluetooth框架,該框架支持藍牙4.0協議,目前使用的非常多
主要概念:
中心設備:對應的類是CBCentralManager,中心設備就是手機,用于掃描和連接藍牙
周邊設備:對應的類是CBPeripheral,周邊設備就是藍牙設備,用于數據的傳輸處理,當然了沒有絕對的中心設備或者周邊設備,他們是互通的,根據不同的需求,通常一個App中的中心設備和周邊設備的功能是整合在一起的
服務:對應類CBService,一個周邊設備可以包含若干個服務,通過廣播的形式供中心設備發現和使用。
特征:對應類CBCharacteristic,一個服務中可以包含若干個特征,特征是我們發送或接受數據的地方
UUID:對應類CBUUID,每個周邊設備、服務和特征都有一個UUID來唯一標識自己。UUID有16bit、32bit和128bit三種,進行UUID比較時要將低位的UUID轉換成高位的UUID再進行比較
下面是使用這些類的一般步驟:
1??創建中心設備管理者
中心設備管理者主要用于掃描和連接藍牙設備,
使用[[CBCentralManager alloc] initWithDelegate:self queue:nil]方法創建管理者,
遵守對應的協議CBCentralManagerDelegate
2??使用中心管理者掃描藍牙設備
使用如下方法掃描藍牙
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
參數為nil表示掃描所有藍牙,當掃描到藍牙設備時會調用如下代理方法
-(void)centralManager: didDiscoverPeripheral: advertisementData: RSSI:
需要注意的是每掃描到一個藍牙設備都會調用一次這個方法,RSSI是該藍牙的信號強度,一般來說距離越遠信號越弱,RSSI的值越接近0信號越強。通常會在這個方法中將信號較弱的藍牙設備屏蔽掉,也可以在這個方法中根據藍牙名稱peripheral.name來過濾不想要的藍牙設備
將掃描到的藍牙設備用數組保存起來,通過tableview展示出來
3??連接藍牙
點擊tableview的某一行,連接該行對應的藍牙設備,使用如下方法連接藍牙
[self.centralManager connectPeripheral:peripheral options:nil];
連接成功后會調用如下代理方法
-(void)centralManager: didConnectPeripheral:
在代理方法中設置已連接的藍牙設備的代理peripheral.delegate = self;
遵守相關協議CBPeripheralDelegate,該協議和數據傳輸有關。
在代理方法中還要做的一件事就是搜索服務,使用如下方法搜索服務
[peripheral discoverServices:nil];
4??搜索服務
在上一步我們已經搜索了服務,服務搜索到之后會調用如下代理方法
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:
需要注意的是這個方法只調用一次,并且所有的服務通過peripheral.services來獲取
5??搜索特征
我們需要在上一步的代理方法中逐個的搜索所有服務中的所有特征,通過for循環來遍歷吧
for (CBService *service in peripheral.services) {
[peripheral discoverCharacteristics:nil forService:service];
}
搜索到特征后會調用如下代理方法
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:
需要注意的是有多少個服務這個方法就會調用多少次,服務中的特征通過如下方法獲得
service.characteristics
6??處理特征
我們接收、讀取和監聽數據的所有操作都是在特征這一層來完成的,常用的特征有下面幾個,特征的功能是公司定的,一切以實際為準,下面三個是常用的功能。
1、用于讀取數據的特征:有些特征是負責讀取數據的,如果我們要主動讀取一些數據,需要通過這個特征來讀取數據,對應的讀取方法如下
[self.currentConnectPeripheral readValueForCharacteristic:ReadCharacteristic];
讀取到數據后會調用如下代理方法,
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:
通過characteristic.value獲取數據然后進行解析
2、用于寫入數據的特征:有些特征是負責寫入操作的,比如我們要同步時間,需要將手機的時間發送到藍牙設備,這個時候就需要用到這個特征,調用如下方法寫入數據
[self.currentConnectPeripheral writeValue:data forCharacteristic:WriteCharacteristic type:CBCharacteristicWriteWithResponse];
寫入成功后會調用如下代理方法
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
需要注意的是藍牙4.0協議,每次發送或接收的最大數據長度為20個字節,如果數據過長則需要分包發送,接收的時候也是一樣,如果命令過長則會分包接收命令,然后拼接命令
3、用于監聽數據的特征:有些特征是用于數據監聽的,比如藍牙設備會不定時的發送一些信息,如溫度信息,由于不定時,所以讀取數據的特征不太現實,只能用監聽數據的特征,只要該特征中的數值發生變化就會觸發如下代理方法
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:
不過這個特征比較特殊,需要多一步操作,設置監聽,使用如下方法設置監聽
[self.currentConnectPeripheral setNotifyValue:YES forCharacteristic:NotifyCharacter];
以下是完整的藍牙管理類代碼
.h
#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>
#define BLEMANAGER [BLEManager manager]
@protocol BLEManagerDelegate <NSObject>
@optional
//已經發現藍牙設備
-(void)BLE_didDiscoverPeripherals:(NSMutableArray<CBPeripheral *>*)peripherals;
//已經連接到藍牙設備
-(void)BLE_didConnectPeripheral:(CBPeripheral*)peripherial;
//連接錯誤
-(void)BLE_connectError;
//已經斷開藍牙外設
-(void)BLE_didDisconnectPeripheral:(CBPeripheral*)peripheral;
//已經掃描完畢
-(void)BLE_didEndScan;
@end
@interface BLEManager : NSObject
@property (nonatomic,weak) id<BLEManagerDelegate> delegate;
//掃描到的藍牙設備(通過了名字和信號強度rssi的篩選)
@property (nonatomic,strong) NSMutableArray<CBPeripheral*> *discoveredPeripherals;
//單粒方法
+ (instancetype)manager;
//初始化中心管理者
- (void)CL_initializeCentralManager;
#pragma mark - d
//藍牙是否正在連接
- (BOOL)CL_IsConnecttingPeripheral;
//開始掃描外圍設備 設置超時
- (void)CL_StartScanDeviceWithTimeout:(NSTimeInterval)timeout;
//開始連接指定的藍牙設備
- (void)CL_StartConnectPeripheral:(CBPeripheral *)peripheral;
//斷開當前連接的藍牙設備
- (void)CL_DisconnectPeripheral:(CBPeripheral*)peripheral;
//寫入數據
-(void)CL_writeValue:(int)serviceUUID characteristicUUID:(int)characteristicUUID data:(NSData *)data
@end
.m
#import "BLEManager.h"
#import <CoreBluetooth/CoreBluetooth.h>
#define RSSI_blueTooth 80 //可接受的外圍藍牙信號強度最低值 rssi通常為負數 只要大于-50 藍牙信號強度就可接受
typedef struct _CHAR{
char buff[1000];
}CHAR_STRUCT;
@interface BLEManager()<CBCentralManagerDelegate,CBPeripheralDelegate>
//藍牙中心管理者
@property (nonatomic,strong) CBCentralManager *centralManager;
//掃描狀態
@property (nonatomic,assign) BOOL isScan;
//掃描超時定時器
@property (nonatomic,strong) NSTimer *timeoutTimer;
//準備連接的藍牙外設
@property (nonatomic,strong) CBPeripheral *prepareConnectPeripheral;
//當前已經連接的藍牙外設
@property (nonatomic,strong) CBPeripheral *currentConnectPeripheral;
//藍牙的連接狀態 是否正在連接
@property (nonatomic,assign) BOOL isConnecttingBluetooth;
//特征值
@property (nonatomic,strong) CBCharacteristic *writeCharacteristic; //寫入通道
@property (nonatomic,strong) CBCharacteristic *readCharacteristic; //讀取通道
@property (nonatomic,strong) CBCharacteristic *notifyCharacteristic; //監聽通道
//藍牙返回數據 或命令
@property (nonatomic,strong) NSMutableArray *deviceCallBack;
@end
@implementation BLEManager
#pragma mark - 初始化管理者
//單粒方法
+ (instancetype)manager
{
static BLEManager *sharedInstance = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
//初始化中心管理者 只需在appdelegate中初始化一次即可
- (void)CL_initializeCentralManager
{
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
self.isScan = NO;
self.isConnecttingBluetooth = NO;
self.discoveredPeripherals = [NSMutableArray array];
self.deviceCallBack = [NSMutableArray array];
}
#pragma mark - 藍牙的掃描和連接
//藍牙是否正在連接
- (BOOL)CL_IsConnecttingPeripheral{
return self.isConnecttingBluetooth;
}
//開始掃描外圍設備 設置超時
- (void)CL_StartScanDeviceWithTimeout:(NSTimeInterval)timeout
{
//設置默認超時時間
if (timeout < 0 || timeout > 60) {
timeout = 5;
}
if (self.centralManager.state != CBManagerStatePoweredOn) {
NSLog(@"手機藍牙未開啟!");
return;
}
//停止上一次的定時器
[self destroyTimeoutTimer];
//掃描前清空之前的設備記錄
[self.discoveredPeripherals removeAllObjects];
//再重新開啟一個定時器
if (self.timeoutTimer == nil) {
self.timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:timeout target:self selector:@selector(timeout) userInfo:nil repeats:NO];
}
[self.centralManager stopScan];
//開始掃描之前一定要判斷centralManager的state是否為CBManagerStatePoweredOn 否則將不會掃描
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
self.isScan = YES;
}
//掃描超時回調
-(void)timeout{
[self destroyTimeoutTimer];
[self.centralManager stopScan];
self.isScan = NO;
if ([self.delegate respondsToSelector:@selector(BLE_didEndScan)])
[self.delegate BLE_didEndScan];
}
//銷毀定時器
-(void)destroyTimeoutTimer
{
[self.timeoutTimer invalidate];
self.timeoutTimer = nil;
}
//開始連接指定的藍牙外設
-(void)CL_StartConnectPeripheral:(CBPeripheral *)peripheral
{
if (self.centralManager.state != CBManagerStatePoweredOn) {
NSLog(@"手機藍牙未開啟!");
return;
}
peripheral.delegate = self; //設置外設代理
if (peripheral) {
self.prepareConnectPeripheral = peripheral;
peripheral.delegate = self;
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
//斷開當前連接的藍牙
-(void)CL_DisconnectPeripheral:(CBPeripheral*)peripheral{
[self.centralManager cancelPeripheralConnection:peripheral];
}
#pragma mark - 數據寫入和讀取
/**
* 通用寫入數據,發送給外設
*/
-(void)CL_writeValue:(int)serviceUUID characteristicUUID:(int)characteristicUUID data:(NSData *)data{
CBUUID *su = [self UUIDWithNumber:serviceUUID]; //獲得serviceUUID 服務uuid
CBUUID *cu = [self UUIDWithNumber:characteristicUUID]; //獲得characteristicUUID 特征uuid
CBService *service = [self searchServiceWithUUID:su]; //搜索對應的服務
if (service) {
CBCharacteristic *characteristic = [self searchCharacteristicWithUUID:cu andService:service];
if (characteristic) {
[self.currentConnectPeripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
}
}
// 通用從外設讀取數據
-(void)CL_readValue:(int)serviceUUID characteristicUUID:(int)characteristicUUID{
CBUUID *su = [self UUIDWithNumber:serviceUUID]; //獲得serviceUUID 服務uuid
CBUUID *cu = [self UUIDWithNumber:characteristicUUID]; //獲得characteristicUUID 特征uuid
CBService *service = [self searchServiceWithUUID:su]; //搜索對應的服務
if (service) {
CBCharacteristic *characteristic = [self searchCharacteristicWithUUID:cu andService:service];
if (characteristic) {
[self.currentConnectPeripheral readValueForCharacteristic:characteristic];
}
}
}
// 通用該外設注冊通知狀態是否活躍 根據isActive值 開啟或者關閉對某個特征值的監聽
-(void)CL_notification:(int)serviceUUID characteristicUUID:(int)characteristicUUID isActive:(BOOL)isActive{
CBUUID *su = [self UUIDWithNumber:serviceUUID]; //獲得serviceUUID 服務uuid
CBUUID *cu = [self UUIDWithNumber:characteristicUUID]; //獲得characteristicUUID 特征uuid
CBService *service = [self searchServiceWithUUID:su];
if (service) {
CBCharacteristic *characteristic = [self searchCharacteristicWithUUID:cu andService:service];
if (characteristic) {
//設置特征值變化的通知
[self.currentConnectPeripheral setNotifyValue:isActive forCharacteristic:characteristic];
}
}
}
//在服務中搜索特征
-(CBCharacteristic *)searchCharacteristicWithUUID:(CBUUID *)cu andService:(CBService *)service
{
CBCharacteristic *characteristic = nil; //在服務中搜索特征
for(int i=0; i < service.characteristics.count; i++) {
CBCharacteristic *c = [service.characteristics objectAtIndex:i];
if ([c.UUID isEqual:cu]) {
characteristic = c;
break;
}
}
return characteristic;
}
//搜索服務
-(CBService *)searchServiceWithUUID:(CBUUID *)su
{
CBService *service = nil; //搜索對應的服務
for(int i = 0; i < self.currentConnectPeripheral.services.count; i++) {
CBService *s = [self.currentConnectPeripheral.services objectAtIndex:i];
if ([s.UUID isEqual:su]) {
service = s;
break;
}
}
return service;
}
-(CBUUID *)UUIDWithNumber:(int)number
{
UInt16 s = [self _swap:number];
NSData *sd = [[NSData alloc] initWithBytes:(char *)&s length:2];
return [CBUUID UUIDWithData:sd];
}
- (UInt16)_swap:(UInt16) s{
UInt16 temp = s << 8;
temp |= (s >> 8); //表示 temp = temp | (s >> 8) 按位或
return temp;
}
//十六進制的字符串To String
- (NSString *)stringFromHexString:(NSString *)hexString {
if (([hexString length] % 2) != 0)
return nil;
NSMutableString *string = [NSMutableString string];
for (NSInteger i = 0; i < [hexString length]; i += 2) {
NSString *hex = [hexString substringWithRange:NSMakeRange(i, 2)];
NSInteger decimalValue = 0;
sscanf([hex UTF8String], "%lx", &decimalValue);
[string appendFormat:@"%ld", (long)decimalValue];
}
return string;
}
#pragma mark - CBCentralManagerDelegate 中心設備代理方法
/**
* 中心管理者更新狀態
*/
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
if (central.state == CBManagerStatePoweredOff) {
self.isConnecttingBluetooth = NO;
self.currentConnectPeripheral = nil;
if ([self.delegate respondsToSelector:@selector(BLE_didDisconnectPeripheral:)])
[self.delegate BLE_didDisconnectPeripheral:self.currentConnectPeripheral];
NSLog(@"斷開連接");
}
}
//中心設備發現外圍設備的回調
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI
{
if (peripheral.name == nil) {
return;
}
//按名字和信號強度篩選藍牙設備
if ([peripheral.name containsString:@"BLE"]) {
if (RSSI.intValue > -RSSI_blueTooth) {
// NSLog(@"掃描到的設備 = %@",peripheral);
BOOL isRepetitive = NO; //重復標記
for (CBPeripheral *peripher in self.discoveredPeripherals){ //查重
if ([peripheral.identifier.UUIDString isEqualToString:peripher.identifier.UUIDString])
isRepetitive = YES; //有重復的
}
if (!isRepetitive)
[self.discoveredPeripherals addObject:peripheral];
}
}
//通知代理
if ([self.delegate respondsToSelector:@selector(BLE_didDiscoverPeripherals:)])
[self.delegate BLE_didDiscoverPeripherals:self.discoveredPeripherals];
}
//中心設備成功連接外圍設備
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
self.isConnecttingBluetooth = YES;
self.currentConnectPeripheral = peripheral;
//搜索服務
[self.currentConnectPeripheral discoverServices:nil];
//通知代理
if ([self.delegate respondsToSelector:@selector(BLE_didConnectPeripheral:)])
[self.delegate BLE_didConnectPeripheral:peripheral];
NSLog(@"已連接的設備%@",peripheral);
}
//連接失敗
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error{
if ([self.delegate respondsToSelector:@selector(BLE_connectError)])
[self.delegate BLE_connectError];
}
//設備斷開連接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error
{
self.isConnecttingBluetooth = NO;
self.currentConnectPeripheral = nil;
//通知代理
if ([self.delegate respondsToSelector:@selector(BLE_didDisconnectPeripheral:)])
[self.delegate BLE_didDisconnectPeripheral:peripheral];
NSLog(@"斷開的設備%@",peripheral);
}
#pragma mark - CBPeripheralDelegate 藍牙外設代理
//發現藍牙的服務
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error
{
if (error) {
return;
}
NSLog(@"發現服務 = %@ count = %lu",peripheral.services,peripheral.services.count);
//去發現所有服務中的所有特征
for (CBService *service in peripheral.services) {
[peripheral discoverCharacteristics:nil forService:service];
}
}
//發現服務中的特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error
{
if (error) {
return;
}
//公司不同 規定的特征的UUID可能也不同,
for (CBCharacteristic *character in service.characteristics) {
NSLog(@"發現服務中的特征%@",character);
// 寫入數據的特征值 0xXXXX
if ([character.UUID isEqual:[CBUUID UUIDWithString:@"0000XXXX-0000-1000-8000-00805F9B34FB"]]) {
self.writeCharacteristic = character;
continue;
}
// 讀取數據的特征值 0xXXXX
if ([character.UUID isEqual:[CBUUID UUIDWithString:@"0000XXXX-0000-1000-8000-00805F9B34FB"]] ) {
self.readCharacteristic = character;
[self CL_readValue:0xXXXX characteristicUUID:0xXXXX];
continue;
}
// 注冊監聽通道的特征值 0xXXXX
if ([character.UUID isEqual:[CBUUID UUIDWithString:@"0000XXXX-0000-1000-8000-00805F9B34FB"]] ) {
self.notifyCharacteristic = character;
[self.currentConnectPeripheral setNotifyValue:YES forCharacteristic:character]; //監聽通道開啟監聽
continue;
}
}
}
//特征值更新
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
if (error) {
NSLog(@"特征值更新錯誤%@",error);
return;
}
//處理特征值,解析數據,公司不同解析規則也不同
NSData *receiveData = characteristic.value;
Byte *byte = (Byte *)[receiveData bytes];
[self.deviceCallBack removeAllObjects];
[self.deviceCallBack addObject:receiveData];
if (self.delegate && [self.delegaterespondsToSelector:@selector(getValueForPeripheral)]) {
[self.delegate getValueForPeripheral];
}
}
//特征值寫入(修改)成功
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
if (error) {
NSLog(@"寫入失敗%@",error);
}else{
NSLog(@"寫入成功");
}
}
//特征值監聽狀態改變
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
{
NSLog(@"特征監聽狀態改變 = %@",characteristic);
}
@end