一、结构图
二、服务和特征
每个设备都会有一些服务,每个服务里面都会有一些特征,特征就是具体键值对,提供数据的地方。每个特征属性分为这么几种:读,写,通知这么几种方式。
外设、服务、特征间的关系
蓝牙中心模式流程
- 建立中心角色
- 扫描外设(discover)
- 连接外设(connect)
- 扫描外设中的服务和特征(discover)
- 4.1 获取外设的services
- 4.2 获取外设的Characteristics,获取Characteristics的值,获取Characteristics的Descriptor和Descriptor的值
- 与外设做数据交互(explore and interact)
- 订阅Characteristic的通知
- 断开连接(disconnect)
蓝牙外设模式流程
- 启动一个Peripheral管理对象
- 本地Peripheral设置服务,特性,描述,权限等等
- Peripheral发送广告
- 设置处理订阅、取消订阅、读characteristic、写characteristic的委托方法
三、创建Peripheral
3.1 导入头文件、设置代理、设置宏
//这里的uuid是通过mac终端生成的
#define kServiceUUID @"51BE80A3-9A24-4777-8180-3DBA54A0786A" //服务的UUID
#define kCharacteristicUUID @"ABA43AD0-6DA8-44C5-ABA5-9301092B8B79" //特征的UUID
@interface ViewController ()<CBPeripheralManagerDelegate>
@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
@property (nonatomic, strong) CBMutableCharacteristic *customCharacteristic;
@property (nonatomic, strong) CBMutableService *customService;
@property (strong,nonatomic) NSMutableArray *centralM;
@property (weak, nonatomic) IBOutlet UITextView *lof;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// ① 创建Peripheral Manager
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
}
3.2 调用代理方法
//判断当前设备蓝牙是否可用,如果可用,调用setupService方法
-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
NSLog(@"%ld",(long)peripheral.state);
switch (peripheral.state) {
case CBPeripheralManagerStatePoweredOn:
[self setupService];
break;
case CBPeripheralManagerStateResetting:
NSLog(@"CBPeripheralManagerStateResetting");
break;
case CBPeripheralManagerStateUnsupported:
NSLog(@"CBPeripheralManagerStateUnsupported");
break;
default:
NSLog(@"Peripheral Manager did change state");
break;
}
}
//创建Serivice和Characteristic 并添加到Peripheral
- (void)setupService{
NSLog(@"setupService!!");
[self writeToLog:@"init setupService..."];
//特征值
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
// 这里 properties 和 permissions 代表着不同的权限 按照业务需要添加 比如 读写操作等
self.customCharacteristic = [[CBMutableCharacteristic alloc] initWithType: characteristicUUID properties:CBCharacteristicPropertyWrite | CBCharacteristicPropertyRead | CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsWriteable | CBAttributePermissionsReadable];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
self.customService = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
[self.customService setCharacteristics:@[self.customCharacteristic]];
[self.peripheralManager addService:self.customService];
}
当调用addService方法的时候,会调用代理方法 didAddService
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error {
[self writeToLog:@"init didAddService..."];
NSLog(@"init didAddService");
if (error == nil) {
// 外围设备开始广播服务 这里指定ServiceUUID用于中心设备找到他
[self.peripheralManager startAdvertising:
@{ CBAdvertisementDataLocalNameKey : kPeripheralName, CBAdvertisementDataServiceUUIDsKey :@[[CBUUID UUIDWithString:kServiceUUID]] }];
}
}
开启广播后,会调用另外一个代理方法 peripheralManagerDidStartAdvertising
-(void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error{
if(error){
NSLog(@"error====%@",error);
}else{
[self writeToLog:@"init peripheralManagerDidStartAdvertising..."];
NSLog(@"init peripheralManagerDidStartAdvertising");
}
}
此时,就等待central设备连接,如果连接成功,会调用代理方法 didSubscribeToCharacteristic
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
[self updateCharacteristicValue];
if (![self.centralM containsObject:central]) {
[self.centralM addObject:central];
}
# [self writeToLog:@"init didSubscribeToCharacteristic"];
NSLog(@"init didSubscribeToCharacteristic");
}
如果central想要对我们的characteristic进行读写操作,会调用下面的代理方法,这里会有权限设置,需要在前面创建的时候指定
-(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests{
NSLog(@"didReceiveWriteRequests");
CBATTRequest *request = requests[0];
//判断是否有写数据的权限
if (request.characteristic.properties & CBCharacteristicPropertyWrite) {
//需要转换成CBMutableCharacteristic对象才能进行写值
CBMutableCharacteristic *c =(CBMutableCharacteristic *)request.characteristic;
c.value = request.value;
NSString *dataStr = [[NSString alloc]initWithData:request.value encoding:NSUTF8StringEncoding];
[self writeToLog:[NSString stringWithFormat:@"收到central发来的数据%@",dataStr]];
[self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
}else{
[self.peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
}
}
-(void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request{
NSLog(@"didReceiveReadRequest");
//判断是否有读数据的权限
if (request.characteristic.properties & CBCharacteristicPropertyRead) {
NSData *data = request.characteristic.value;
[request setValue:data];
//对请求作出成功响应
[self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
}else{
[self.peripheralManager respondToRequest:request withResult:CBATTErrorWriteNotPermitted];
}
}
四、创建central
4.1导入头文件,创建对象,设置代理
#import "ClientViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>
@interface ClientViewController () <CBCentralManagerDelegate, CBPeripheralDelegate>
@property (nonatomic, strong) CBCentralManager *manager;
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
4.2调用代理方法 CBCentralManagerDelegate(主要用于连接)
//初始化的时候回调用此代理方法检查蓝牙状态
- (void)centralManagerDidUpdateState: (CBCentralManager *)central {
switch (central.state) {
case CBCentralManagerStatePoweredOn:
// Scans for peripheral with uuid
NSLog(@"自动搜索!");
[self writeToLog:@"正在搜索..."];
[self.manager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
break;
case CBCentralManagerStatePoweredOff:
NSLog(@"蓝牙没有打开,请先打开蓝牙");
break;
case CBCentralManagerStateUnsupported:
NSLog(@"当前设备不支持蓝牙");
break;
default:
NSLog(@"Central Manager did change state");
break;
}
}
如果蓝牙可用,就开始扫描设备 通过下面方法,这里的kServiceUUID是在Peripheral端定义好的,用于找我们需要的service
[self.manager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
如果找到了对于的服务,就会调用代理方法didDiscoverPeripheral
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"init didDiscoverPeripheral");
[self writeToLog:@"发现peripheral..."];
if (self.peripheral != peripheral) {
self.peripheral = peripheral;
NSLog(@"Connecting to peripheral %@", peripheral);
// 这里尝试连接我们发现的 peripheral
[self.manager connectPeripheral:peripheral options:nil];
}
}
连接成功和连接失败都会调用对应的代理方法
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"init didFailToConnectPeripheral");
[self writeToLog:@"连接peripheral失败..."];
}
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
NSLog(@"init didConnectPeripheral");
// Stops scanning for peripheral
[self.manager stopScan];
[self writeToLog:@"连接peripheral成功..."];
// Clears the data that we may already have
[self.data setLength:0];
// Sets the peripheral delegate
[self.peripheral setDelegate:self];
// Asks the peripheral to discover the service
[self.peripheral discoverServices:@[ [CBUUID UUIDWithString:kServiceUUID] ]];
}
连接成功后,根据对于的UUID去寻找对于的服务,这里的UUID在Peripheral端定义好的,如果发现服务后会调用didDiscoverServices方法
4.3调用代理方法 CBPeripheralDelegate(主要用于和peripheral进行交互)
- (void)peripheral:(CBPeripheral *)aPeripheral didDiscoverServices:(NSError *)error {
if (error) {
NSLog(@"Error discovering service: %@", [error localizedDescription]);
// [self cleanup];
return;
}
for (CBService *service in aPeripheral.services) {
NSLog(@"Service found with UUID: %@",service.UUID);
// Discovers the characteristics for a given service
// 这里可以对特定service 找到特定characteristics
if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
[self writeToLog:[NSString stringWithFormat:@"发现service,service.UUID:%@",service.UUID]];
[self.peripheral discoverCharacteristics:@[[CBUUID UUIDWithString: kCharacteristicUUID]] forService:service];
}
}
}
如果找到对于的charcateristic的话,会调用对于代理方法,可以在这里面进行处理
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService: (CBService *)service error:(NSError *)error {
if (error) {
NSLog(@"Error discovering characteristic:%@", [error localizedDescription]);
// [self cleanup];
return;
}
if ([service.UUID isEqual:[CBUUID UUIDWithString: kServiceUUID]]) {
for (CBCharacteristic *characteristic in service.characteristics) {
NSLog(@"遍历服务!");
[self writeToLog:@"遍历characteristics..."];
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {
self.characteristic = characteristic;
//1.调用此方法会调用didUpdateNotificationStateForCharacteristic 这个代理方法
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
//2.或者不调用代理方法 直接读取
// [peripheral readValueForCharacteristic:characteristic];
// if (characteristic.value) {
// NSString *value = [[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
// }
}
}
}
}
如果我们用方式1 对我们的characteristic 进行处理的话,会调用对应代理方法didUpdateNotificationStateForCharacteristic
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic: (CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"Error changing notification state:%@", error.localizedDescription);
}
// Exits if it's not the transfer characteristic
if (![characteristic.UUID isEqual:[CBUUID UUIDWithString:kCharacteristicUUID]]) {
return;
}
// Notification has started
if (characteristic.isNotifying) {
[self writeToLog:@"监听characteristic的变化..."];
NSLog(@"Notification began on %@", characteristic);
// 这里我们进行readValueForCharacteristic 操作,会调用对于的代理方法didUpdateValueForCharacteristic
[peripheral readValueForCharacteristic:characteristic];
} else { // Notification has stopped
// so disconnect from the peripheral
NSLog(@"Notification stopped on %@. Disconnecting", characteristic);
[self.manager cancelPeripheralConnection:self.peripheral];
}
}
// 在这个方法中我们能拿到由peripheral传来的特定characteristic
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSString *dataStr = [[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
[self writeToLog:[NSString stringWithFormat:@"读取characteristic.value = %@",dataStr]];
NSLog(@"characteristic===%@",characteristic);
}
4.4传递数据给peripheral
- (IBAction)writeData:(id)sender {
NSString *valueStr =[NSString stringWithFormat:@"UgenCentral--%@",[NSDate date]];
NSData *value = [valueStr dataUsingEncoding:NSUTF8StringEncoding];
NSLog(@"self.characteristic===%@",self.characteristic);
//通过下面这个方法可以给peripheral更新特性 如果type 选择withResponse的话,还会调用下面的代理方法
[self.peripheral writeValue:value forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
[self writeToLog:[NSString stringWithFormat:@"central端更新特性:%@",valueStr]];
}
//会告诉你是否成功
//我们刚刚穿的数据可以在 第三节的 didReceiveWriteRequests代理方法中找到
-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"error wtrting characteristic value: %@",[error localizedDescription]);
}
[self writeToLog:@"成功给外围设备写数据"];
}