蓝牙 BlueTooth Low Energy (BLE)

来源:互联网 发布:stc单片机检测原理 编辑:程序博客网 时间:2024/05/01 07:18
  • 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.发起链接状态(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 <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




0 0
原创粉丝点击