每个设备会有很多服务
,每个服务中包含很多字段
,这些字段的权限一般分为读(read),写(write),通知(notify)几种,就是我们连接设备后具体需要操作的内容
手机作为外设
连接其他中心设备操作的场景
每个设备都会有1个or多个服务
每个服务里都会有1个or多个特征
特征就是具体键值对,提供数据的地方
每个特征属性分为:读,写,通知等等
2.扫描外设(Discover Peripheral)
3.连接外设(Connect Peripheral)
4.扫描外设中的服务和特征(Discover Services And Characteristics)
4.1 获取外设的services
4.2 获取外设的Characteristics,获取characteristics的值,,获取Characteristics的Descriptor和Descriptor的值
5.利用特征与外设做数据交互(Explore And Interact)
6.订阅Characteristic的通知
7.断开连接(Disconnect)
1.启动一个Peripheral管理对象
2.本地peripheral设置服务,特征,描述,权限等等
3.peripheral发送广告
4.设置处理订阅,取消订阅,读characteristic,写characteristic的代理方法
1.待机状态(standby):设备没有传输和发送数据,并且没有连接到任何外设
2.广播状态(Advertiser):周期性广播状态
3.扫描状态(Scanner):主动搜索正在广播的设备
4.发起链接状态(Initiator):主动向扫描设备发起连接
5.主设备(Master):作为主设备连接到其它设备.
6.从设备(Slave):作为从设备链接到其它设备
准备(Standby)
广播(Advertising)
监听扫描(Scanning)
发起连接(Initiating)
已连接(Connected)
/*******************************************************************************************************/
1.待机状态(standby):设备没有传输和发送数据,并且没有连接到任何外设
2.广播状态(Advertiser):周期性广播状态
3.扫描状态(Scanner):主动搜索正在广播的设备
4.发起链接状态(Initiator):主动向扫描设备发起连接
5.主设备(Master):作为主设备连接到其它设备.
6.从设备(Slave):作为从设备链接到其它设备
准备(Standby)
广播(Advertising)
监听扫描(Scanning)
发起连接(Initiating)
已连接(Connected)
#import "CentralViewController.h"
#import
#define SERVICE_UUID @"307846BD-EDB4-4E3B-A0A7-52B2E5AFA760"
#define CHARACTERISTIC_UUID @"C430B109-A222-44A9-B75D-49D89BD97642"
#define BLUETOOTH_PIC_END @"PIC_END"
#define BLUETOOTH_TEXT_END @"TEXT_END"
#define MAX_BYTES 20
@interface CentralViewController ()
{
CBCentralManager * _manager;
UILabel * _statusLb;
UILabel * _showLb;
}
@property (nonatomic,strong) CBPeripheral * discovedPeripheral;
@property (nonatomic,strong) NSMutableData * data;
@end
@implementation CentralViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// 创建中心管理器对象
_manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()];
_data = [NSMutableData data];
_statusLb = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)];
_statusLb.text = @"扫描外设中...";
[self.view addSubview:_statusLb];
_showLb = [[UILabel alloc] initWithFrame:CGRectMake(0, 30, 320, 100)];
_showLb.numberOfLines = 0;
_showLb.text = @"接受到的数据:";
[self.view addSubview:_showLb];
}
-(void)scan{
// 是否允许中央设备多次收到曾经监听到的设备的消息,这样来监听外围设备联接的信号强度,以决定是否增大广播强度,为YES时会多耗电
[_manager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]
options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
}
-(void)clearUp{
if (![self.discovedPeripheral isConnected]) {
return;
}
if (self.discovedPeripheral.services!=nil) {
for (CBService*server in self.discovedPeripheral.services) {
if (server.characteristics!=nil) {
for (CBCharacteristic*chatacter in server.characteristics) {
if ([chatacter.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_UUID]]) {
// 是否订阅
if (chatacter.isNotifying) {
// 如果订阅 取消订阅
[self.discovedPeripheral setNotifyValue:NO forCharacteristic:chatacter];
return;
}
}
}
}
}
}
// 连接没有订阅 断开连接
[_manager cancelPeripheralConnection:self.discovedPeripheral];
}
#pragma mark - CBCentralManagerDelegate
// 检测中央设备状态
-(void)centralManagerDidUpdateState:(CBCentralManager *)central
{
if (central.state!=CBCentralManagerStatePoweredOn) {
NSLog(@"蓝牙关闭");
return;
}
// 开启检测
[self scan];
}
// 当外围设备广播同样的UUID信号 被发现时 函数被调用 RSSI接收的信号强度指示 足够近才能连接
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(@"Discovered %@ at %@", peripheral.name, RSSI);
// 判断是不是我们监听到的外围设备
if (self.discovedPeripheral != peripheral) {
self.discovedPeripheral = peripheral;
// 连接周边
[_manager connectPeripheral:peripheral options:nil];
NSLog(@"Connecting to peripheral %@", peripheral);
}
}
// 连接上外围设备后我们就要找到外围设备的服务特性
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
// 连接完成后,就停止检测
[_manager stopScan];
[self.data setLength:0];
// 确保我们收到的外围设备连接后的回调代理函数
peripheral.delegate=self;
// 让外围设备找到与我们发送的UUID所匹配的服务
// 生成UUID命令:uuidgen
[peripheral discoverServices:@[[CBUUID UUIDWithString:SERVICE_UUID]]];
}
// 发现了服务
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
if (error) {
NSLog(@"Errordiscover:%@",error.localizedDescription);
[self clearUp];
return;
}
// 找到我们想要的特性
// 遍历外围设备
for (CBService*server in peripheral.services) {
// 寻找指定UUID的特征
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:CHARACTERISTIC_UUID]] forService:server];
}
}
// 当发现传送服务特性后我们要订阅他 来告诉外围设备我们想要这个特性所持有的数据
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error) {
NSLog(@"error %@",[error localizedDescription]);
[self clearUp];
return;
}
// 检查特性
for (CBCharacteristic*characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_UUID]]) {
// 有来自外围的特性,找到了,就订阅他
// 如果第一个参数是yes的话,就是允许代理方法peripheral:didUpdateValueForCharacteristic:error: 来监听 第二个参数 特性是否发生变化
// 订阅特征
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
NSLog(@"订阅成功");
}
}
}
// 外围设备让我们知道,我们订阅和取消订阅是否发生
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
NSLog(@"error %@",error.localizedDescription);
}
// 如果不是我们要的特性就退出
if (![characteristic.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_UUID]]) {
return;
}
if (characteristic.isNotifying) {
NSLog(@"外围特性通知开始");
_statusLb.text = @"连接成功 等待接受数据";
}else{
NSLog(@"外围设备特性通知结束,也就是用户要下线或者离开%@",characteristic);
// 断开连接
[_manager cancelPeripheralConnection:peripheral];
}
}
// 接受蓝牙传递的数据
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
return;
}
// characteristic.value 是特性中所包含的数据
NSString * stringFromData=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
if ([stringFromData isEqualToString:BLUETOOTH_PIC_END]) {
// 接受文字
NSString * str= [[NSString alloc]initWithData:self.data encoding:NSUTF8StringEncoding];
_showLb.text = str;
self.data.length = 0;
#if 0
// 接受图片
UIImage * img = [UIImage imageWithData:self.data];
UIImageView * imgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 150, 320, 200)];
imgView.image = img;
[self.view addSubview:imgView];
#endif
#if 0
// 取消订阅 断开蓝牙连接
[peripheral setNotifyValue:NO forCharacteristic:characteristic];
[_manager cancelPeripheralConnection:peripheral];
#endif
}else{
// 数据没有传递完成,继续传递数据
[self.data appendData:characteristic.value];
}
}
@end
#import "PeripheralViewController.h"
#import
@interface PeripheralViewController ()
{
UILabel * _statusLb;
UITextField * _inputView;
// 当前发送了多少字节
unsigned long _sendBytes;
// 是否发送完成
BOOL _finish;
}
// 周边设备管理类
@property(nonatomic,strong)CBPeripheralManager*peripheralManager;
// 可变服务特性
@property(nonatomic,strong)CBMutableCharacteristic*transferCharacteristic;
@end
@implementation PeripheralViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.peripheralManager=[[CBPeripheralManager alloc]initWithDelegate:self queue:nil];
_statusLb = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200, 30)];
_statusLb.text = @"发送广播中...";
[self.view addSubview:_statusLb];
_inputView = [[UITextField alloc] initWithFrame:CGRectMake(0, 30, 270, 30)];
_inputView.placeholder = @"需要通过蓝牙发送的消息...";
[self.view addSubview:_inputView];
UIButton * b = [UIButton buttonWithType:UIButtonTypeSystem];
[b setFrame:CGRectMake(270, 30, 50, 30)];
[b setBackgroundColor:[UIColor grayColor]];
[b setTitle:@"发送" forState:UIControlStateNormal];
[b addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:b];
}
/*
切割发送二进制数据 一次性发送20字节 直到发送完毕
如果发送完毕 那么最后再发送一个 pic_end字符串给中心
*/
- (void)click
{
// 如果蓝牙数据发送完成了 最后发一个字符串 "pic_end"给中心 表示发送完成
if (_finish) {
//第三个参数代表指定与我们的订阅的中心设备发送,返回一个布尔值,代表发送成功
BOOL didSend=[self.peripheralManager updateValue:[BLUETOOTH_PIC_END dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil];
if (didSend) {
//全部发送完成
_finish = NO;
_sendBytes = 0;
NSLog(@"发送完成");
}
//如果没有发送,我们就要退出并且等待
//peripheralManagerIsReadyToUpdateSubscribers 来再一次调用sendData来发送数据
return;
}
// 如果没有正在发送BluetoothEnd,就是在发送数据
// 发送文字
NSData * sendData=[_inputView.text dataUsingEncoding:NSUTF8StringEncoding];
#if 0
// 发送图片
NSData * sendData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"png"]];
#endif
//判断是否还有剩下的数据
if (_sendBytes >= sendData.length) {
//没有数据,退出即可
return;
}
//如果有数据没有发送完就发送它,除非回调失败或者我们发送完
BOOL didSend=YES;
while (didSend) {
//发送下一块数据,计算出数据有多大
NSInteger amountToSend=sendData.length-_sendBytes;
if (amountToSend>MAX_BYTES) {
//如果剩余的数据还是大于20字节,那么我最多传送20字节
amountToSend=MAX_BYTES;
}
//切出我想要发送的数据 +sendDataIndex就是从多少字节开始向后切多少
NSData*chunk=[NSData dataWithBytes:sendData.bytes+_sendBytes length:amountToSend];
//发送
didSend=[self.peripheralManager updateValue:chunk forCharacteristic:self.transferCharacteristic onSubscribedCentrals:nil];
//如果没发送成功,等待回调发送
if (!didSend) {
return;
}else{
_sendBytes+=amountToSend;
//判断是否发送完
if (_sendBytes>=sendData.length) {
//发送完成,就开始发送结束标示bluetoothEND
_finish = YES;
[self performSelector:@selector(click) withObject:nil afterDelay:0.1];
}
}
}
[self.view endEditing:YES];
}
#pragma mark - CBPeripheralManagerDelegate
// 检测状态
-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
{
if (peripheral.state!=CBPeripheralManagerStatePoweredOn) {
return;
}
// 启动service
// 启动可变服务特性properties:Notify允许没有回答的服务特性,向中心设备发送数据,permissions:read通讯属性为只读
self.transferCharacteristic=[[CBMutableCharacteristic alloc]initWithType:[CBUUID UUIDWithString:CHARACTERISTIC_UUID] properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];
// 创建服务 primary 是首次还是第二次
CBMutableService*transferService=[[CBMutableService alloc]initWithType:[CBUUID UUIDWithString:SERVICE_UUID] primary:YES];
// 把特性加到服务上
transferService.characteristics=@[self.transferCharacteristic];
// 把服务加到管理上
[self.peripheralManager addService:transferService];
// 发送广播,标示是TRANSFER_SERVICE_UUID为对方观察接收的值,2边要对应上
[self.peripheralManager startAdvertising:@{CBAdvertisementDataServiceUUIDsKey:@[[CBUUID UUIDWithString:SERVICE_UUID]]}];
}
// 订阅特性成功 开始发送数据
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
{
NSLog(@"订阅成功");
_statusLb.text = @"连接成功 可以发送消息了";
}
// 中央设备结束订阅时候调用
-(void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic
{
NSLog(@"结束订阅");
_statusLb.text = @"连接断开";
}
// 发送队列满了 需要再次发送
-(void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral
{
// NSLog(@"发送队列已满 再次发送");
[self click];
}
@end