您的位置:首页 > 产品设计 > UI/UE

蓝牙 BlueTooth Low Energy (BLE)

2017-03-17 17:09 429 查看
BLE:(Bluetooth low energy)蓝牙4.0设备因为低耗电,也叫BLE

peripheral,central:外设和中心设备,发起链接的是central(一般是指手机),被链接的设备是peripheral(运动手环)

service and characteristic:(服务和特征)每个设备会提供服务和特征,类似于服务端的API,但是结构不同.
每个设备会有很多服务
,每个服务中包含
很多字段
,这些字段的权限一般分为读(read),写(write),通知(notify)几种,就是我们连接设备后具体需要操作的内容

Description:每个characteristic可以对应一个或者多个Description用于描述characteristic的信息或属性(eg.范围,计量单位)

蓝牙基础知识

CoreBluetooth框架的核心其实是俩东西:peripheral和central,对应他们分别有一组相关的API和类



这两组apif分别对应不同的业务常见:左侧叫中心模式,就是以你的app作为中心,连接其他的外设的场景;而右侧称为外设模式,使用
手机作为外设
连接其他中心设备操作的场景

服务和特征(service and characteristic)

每个设备都会有1个or多个服务

每个服务里都会有1个or多个特征

特征就是具体键值对,提供数据的地方

每个特征属性分为:读,写,通知等等

外设,服务,特征的关系

             


BLE中心模式流程

1.建立中心角色

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)

BLE外设模式流程

1.启动一个Peripheral管理对象

2.本地peripheral设置服务,特征,描述,权限等等

3.peripheral发送广告

4.设置处理订阅,取消订阅,读characteristic,写characteristic的代理方法

蓝牙设备的状态

1.待机状态(standby):设备没有传输和发送数据,并且没有连接到任何外设

2.广播状态(Advertiser):周期性广播状态

3.扫描状态(Scanner):主动搜索正在广播的设备

4.发起链接状态(Init
dfc8
iator):主动向扫描设备发起连接

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 <CoreBluetooth/CoreBluetooth.h>

#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 ()<CBCentralManagerDelegate,CBPeripheralDelegate>
{
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 <CoreBluetooth/CoreBluetooth.h>

@interface PeripheralViewController ()<CBPeripheralManagerDelegate>
{
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: