首先给demo的下载链接:DEMO 提取码:5tsm 如果链接失效请留言,看到后会及时更新.
蓝牙连接的大致流程:
- APP与硬件进行连接、扫描硬件,要把手机作为central来使用,首先创建中心对象完成初始化:
CBCentralManager * central =[[CBCentralManager alloc]initWithDelegate:self queue:queue];
-
初始化后会调用代理
CBCentralManagerDelegate
的
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
方法,在这个方法里CBCentralManagerState是个枚举,可以利用central.state来判断蓝牙开启、关闭、设备是否支持等等。 - 想要连接硬件,首先要扫描,就一句话扫描所有硬件(可以传入指定的Services缩小扫描的结果)
[central scanForPeripheralsWithServices:nil options:nil];
-
扫描完成后,一旦有peripheral被搜寻到,会调用如下方法
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
此方法可以获取扫描到的硬件里的所有数据,可以对数据进行分析,保存我们需要的设备; -
连接自己想要连接的硬件
- 停止扫描:[self.myCenter stopScan];
- 连接:
-(void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options
到目前为止你就成功的连接到了想要连接的指定硬件了,接下来就是要进行对硬件的读与写了。
-
调用连接方法后,连接成功和失败都有回调:
- 如果连接成功会调用如下方法:
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
在此方法里你就要停止扫描了(连接前调用了,这里就可以不调用):- (void)stopScan
还要寻找连接设备里的服务:
- (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs
这里的参数:传入对应的serviceUUIDs 如果为nil,则all services will be discovered
- 如果连接成功会调用如下方法:
- 如果连接失败会调用此方法:
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
(前面如果没有停止扫描,这里要进行停止扫描stopScan)
- 如果连接失败会调用此方法:
-
外设连接之后,找到该设备上的指定服务时会调用CBPeripheralDelegate方法:
(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
如果前面没有指定具体的服务,这里会得到所有的服务,我们可以根据service.UUID去判断需要对那些服务做处理;
通过下面的方法发现服务中的characteristics(特征码):
-(void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs forService:(CBService *)service
-
上面这个方法被调用,找到特征之后调用这个方法:
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
在此方法里遍历service.characteristics用CBCharacteristic来接收;
如果特征是 read 的要调用:
- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic
如果特征是 notify 的要进行订阅,调用方法:
- (void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic
如果特征是写,则我们需要保存这个writeCharact,因为在我们进行写操作时需要使用到它; -
当setNotifyValue方法调用后会调用如下方法:
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
进行判断characteristic是否为isNotifying,如果是 yes就调用:
- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic
是的,这里没有搞错,notify的特征在订阅后要调用readValueForCharacteristic,则使得read和notify的消息回调都在一个方法中得到; -
调用完readValueForCharacteristic:方法后会调用如下方法:
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
此方法获取characteristic.value,这个 value 就是我们想要的notify(read)的值了。 -
如果连接上的设备突然断开,会自动回调下面的方法:
-(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
在此方法里就可以进行断线重连了:
- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options
上面是 read 和notify特征的操作,那么特征为write又改如何操作呢,下面开始介绍 write 的操作;
-
往硬件里写数据要手动调用:
- (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type
CoreBluetooth框架还提供了检测是否写入成功的方法:
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
写入成功时,可以再次调用readValueForCharacteristic:方法,接收写入后设备端的响应;
以上步骤来自:传送门
具体的代码可以看上传的demo,下面是如何搭建一个外设(自己搭建一个外设,意味着我们可以使用两部手机,一个中心一个外设进行数据接收发送了,demo也在上面的下载中)
搭建外设
首先,我们要知道一点,一个外设可以有多个服务,一个服务可以有多个特征(1--->n--->n)图就不画了
导入 #import <CoreBluetooth/CoreBluetooth.h> ,遵从协议<CBPeripheralManagerDelegate>,创建外设管理器,会回调peripheralManagerDidUpdateState方法,必须实现此委托方法,以确保支持蓝牙低功耗并可在本地外围设备上使用
#import <CoreBluetooth/CoreBluetooth.h>
@interface ViewController ()<CBPeripheralManagerDelegate>
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
@property (nonatomic, strong) CBMutableCharacteristic *characteristic;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
NSLog(@"初始化外设管理器");
}
设备的蓝牙状态
CBManagerStateUnknown = 0, 未知
CBManagerStateResetting, 重置中
CBManagerStateUnsupported, 不支持
CBManagerStateUnauthorized, 未验证
CBManagerStatePoweredOff, 未启动
CBManagerStatePoweredOn, 可用
回调方法,如果外设是可用的,可以进行服务和特征的创建了,这里就创建一个服务,服务中就包含一个特征,实际中我们的蓝牙都是包含多个服务多个特征的;
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
if (peripheral.state == CBManagerStatePoweredOn) {
DLog(@"蓝牙状态为-->CBManagerStatePoweredOn");
// 创建Service(服务)和Characteristics(特征)
[self setupServiceAndCharacteristics];
}else{
DLog(@"😡😡当前蓝牙状态为=== %ld 无法作为外设",(long)peripheral.state);
}
}
创建Service(服务)和Characteristics(特征)
#define SERVICE_UUID @"CDD1" //服务的UUDI
#define CHARACTERISTIC_UUID @"CDD2" //特征的UUID
#pragma mark 创建服务和特征
- (void)setupServiceAndCharacteristics {
// 创建服务
CBUUID *serviceID = [CBUUID UUIDWithString:SERVICE_UUID];
CBMutableService *service = [[CBMutableService alloc] initWithType:serviceID primary:YES];
// 创建服务中的特征
CBUUID *characteristicID = [CBUUID UUIDWithString:CHARACTERISTIC_UUID];
CBMutableCharacteristic *characteristic = [
[CBMutableCharacteristic alloc]
initWithType:characteristicID
properties:
CBCharacteristicPropertyRead |
CBCharacteristicPropertyWrite |
CBCharacteristicPropertyNotify
value:nil
permissions:CBAttributePermissionsReadable |
CBAttributePermissionsWriteable
];
// 特征添加进服务
service.characteristics = @[characteristic];
// 服务加入外设管理
[self.peripheralManager addService:service]; //这个方法被调用后,管理者会通知他的代理方法-peripheralManager:didAddService:error: 如果没有Error,你可以开始广播服务了
// 为了手动给中心设备发送数据 ,自己持有这个特征
self.characteristic = characteristic;
}
特征创建说明:
- 每一个服务和特征都需要用一个UUID(unique identifier)去标识,UUID是一个16bit或者128bit的值。如果你要创建你的中央-周边App,你需要创建你自己的128bit的UUID;
- UUID可用终端指令: uuidgen 生成,或者自己写一个16bit的,比如这里的 CDD1 CDD2;
- 初始化特征方法的第三个参数赋值nil,是因为我告诉CoreBluetooth,我将稍后添加一个特征的值。当你想要创建一个动态的数据,一般都这么做。如果你的特征的值是静态的,你可以将它赋值在这.
- 如果是动态的值,通过订阅Notify,可以在数据的回调函数得到实时数据;
最后一个参数是属性的读、写、加密的权限; - 外设 1--->n 服务 1---->n 特征 (都是一对多的关系)
服务特征创建成功,调用如下方法进行广播:
-(void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error{
if (error == nil) {
// 根据服务的UUID开始广播
[self.peripheralManager startAdvertising:@{CBAdvertisementDataServiceUUIDsKey:@[[CBUUID UUIDWithString:SERVICE_UUID]]}];
}else{
DLog(@"🤣🤣服务添加到外设Manager失败,原因 ---> %@",error.localizedDescription);
}
}
到这里,其实中央就可以扫描到我们这个外设了;
下面的代理方法是一些中央的数据回调和外设的数据发送等,具体看代码注释:
/** 中心设备读取数据的时候回调 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request {
// 请求中的数据,这里把文本框中的数据发给中心设备
request.value = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding];
// 成功响应请求
[peripheral respondToRequest:request withResult:CBATTErrorSuccess];
}
/** 中心设备写入数据的时候回调 */
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {
// 写入数据的请求
CBATTRequest *request = requests.lastObject;
NSLog(@"接收到数据%@",request.value);
//把写入的数据显示在文本框中
self.textField.text = [[NSString alloc] initWithData:request.value encoding:NSUTF8StringEncoding];
}
/** 当连接的中心 订阅某个特征的值时,调用 */
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"%s",__FUNCTION__);
}
/**
取消订阅回调
*/
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic {
NSLog(@"%s",__FUNCTION__);
}
- (IBAction)didClickPsot:(id)sender {
BOOL sendSuccess = [self.peripheralManager updateValue:[self.textField.text dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.characteristic onSubscribedCentrals:nil];//发送数据---> 中央需要通过notify接收;
if (sendSuccess) {
NSLog(@"数据发送成功");
}else {
NSLog(@"数据发送失败");
}
}
到这里,我们就可以通过外设的方法:
[self.peripheralManager updateValue:[self.textField.text dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.characteristic onSubscribedCentrals:nil];
像中央发送数据了,这就是蓝牙想我们的手机传输数据的原理了.
因为手机(中央)订阅了我们的特征,所以我们发送的数据能够在中央的代理方法中接收到,这里就是讲了一个大致流程,具体的可以用两部手机跑下demo中的BEL_Center 和BEL_Peripheral感受下.
其实由于IOS的CoreBluetooth的封装,我们集成蓝牙是非常简便的,麻烦的是后面蓝牙发送的数据,前端进行解析时,有时候真的非常非常的麻烦,所以我在前一篇中对蓝牙的一些基本的Byte数组的解析进行了一次归纳,有兴趣的可以看看:01.BLE-数据处理,Byte数组,malloc,位操作
网友评论