蓝牙 BlueTooth Low Energy (BLE)

  • 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和类


蓝牙 BlueTooth Low Energy (BLE)_第1张图片


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

  • 服务和特征(service and characteristic)

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

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

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

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

  • 外设,服务,特征的关系

             蓝牙 BlueTooth Low Energy (BLE)_第2张图片

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.发起链接状态(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




你可能感兴趣的:(文本)