蓝牙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