iOS 蓝牙BLE开发

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 蓝牙BLE开发相关的知识,希望对你有一定的参考价值。

参考技术A GAP(Generic Access Profile):它用来控制设备连接和广播,GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。
GATT(Generic Attribute Profile):BLE连接都是建立在GATT协议之上的。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。
BLE中主要有两个角色:外围设备(Peripheral)和中心设备(Central)。一个中心设备可以连接多个外围设备,一个外围设备包含一个或多个服务(services),一个服务包含一个或多个特征(characteristics)。

使用CoreBluetooth库,创建CBPeripheralManager,实现CBPeripheralManagerDelegate代理

创建完该对象,会回调peripheralManagerDidUpdateState:方法判断蓝牙状态,蓝牙可用,给外设配置服务和特征

注意CBAttributePermissions

当中心设备读写设置CBAttributePermissionsReadEncryptionRequired/CBAttributePermissionsWriteEncryptionRequired权限的Characteristic时,会弹出弹框,请求建立安全连接

给外设配置服务特征后,会调用peripheralManager:didAddService:error: 服务特征全部添加完后发起广播,如果在广播时设置CBAdvertisementDataServiceUUIDsKey,会把该service广播出去,中心设备在扫描时可根据该uuid找到该设备。外围设备靠不断发广播,使中心设备发现它。

当中央端连接上了此设备并订阅了特征时会回调 didSubscribeToCharacteristic:

当接收到中央端读的请求时会调用didReceiveReadRequest:

创建CBCentralManager对象,实现CBCentralManagerDelegate代理

回调centralManagerDidUpdateState:代理方法,当central.state==CBManagerStatePoweredOn时,开启扫描,设置serviceUUIDs可扫描特定外设,CBCentralManagerScanOptionAllowDuplicatesKey设为NO不重复扫描已发现设备,YES是允许

扫描到设备会回调centralManager:didDiscoverPeripheral:advertisementData:RSSI:,RSS绝对值越大,表示信号越差,设备离的越远
关闭扫描

连接设备

发现服务

发现特征

iOS BLE 模块开发总结

 本文默认读者对蓝牙开发有基础的了解, 与外设的交互使用 BabyBluetooth.

      一. 总结的要点如下:

1. iOS 蓝牙与外设连接的步骤.

2. 外设过滤, 服务, 特性.

3. 单模,双模蓝牙.

4. 外设的 UUID.


二. 实际应用场景:

通过 APP 控制荣泰按摩椅, 方便用户切换按摩模式.


三. 第一点对应 OC 代码

1. 在 BabyBluetooth 库中, BabyBluetooth Class 封装了与蓝牙外设交互的所有方法,通过舒适化 BabyBluetooth ,实现代理方法,就可以与外设进行通信.

    //1 初始化
    //2 扫描设备
    //3 连接外设
    //3.1 查找服务
    //3.2 查找到Characteristics并筛选
    //4 写入数据
    //5 读取转换数据

2. 在搜索到蓝牙设备后,要知道每个蓝牙设备都会不少于一个的服务,每个服务都会有不少于一个的特性,我们与外设进行通信,是要约定好这个唯一的特性后,才可正确得进行串口通信.其中特性的属性有读,写两种,我们根据硬件工程师提供给我们的标识,可以获取到这两个需要的特性.

//连接Peripherals成功的委托
    [weakSelf.baby setBlockOnConnected:^(CBCentralManager *central, CBPeripheral *peripheral) 
        NSLog(@"已连接的设备:____%@", peripheral);
        //搜索指定服务
        [peripheral discoverServices: @[[CBUUID UUIDWithString:SERVICE_UUID]]];
        self.isConnected = YES;
        
    ];
//查找服务
    [weakSelf.baby setBlockOnDiscoverServices:^(CBPeripheral *peripheral, NSError *error) 
        
        for (CBService *service in peripheral.services) 
            if([service.UUID isEqual:[CBUUID UUIDWithString:SERVICE_UUID]])
                [peripheral discoverCharacteristics:nil forService:service];
            
        
    ];
//查找到Characteristics的block
    [weakSelf.baby setBlockOnDiscoverCharacteristics:^(CBPeripheral *peripheral, CBService *service, NSError *error) 
        
        for (CBCharacteristic *c in service.characteristics) 
            if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_RX]]) 
                NSLog(@"CHARACTERISTIC_RX:_____%@", c);
                self.characteristicR_x = c;
                if (c.isNotifying) 
                    [self.baby cancelNotify:peripheral characteristic:c];
                 else 
                    [self.peripheral setNotifyValue:YES forCharacteristic:c];
                
            
            
            if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_TX]]) 
                
                self.characteristicT_x = c;
                NSLog(@"CHARACTERISTIC_TX:_____%@", c);
                if (c.isNotifying) 
                    [self.baby cancelNotify:peripheral characteristic:c];
                 else 
                    [self.peripheral setNotifyValue:YES forCharacteristic:c];
                
            
        
    ];


3.蓝牙模块有单模,双模之分,蓝牙双模是指其支持传统蓝牙的Basic Rate(BR)和增强数据率(EDR)工作,也支持最新的低功耗(LE)标准。

4.有一些公司的外部设备的名字都是一样的但是进行连接或者其他操作的时候没必要知道具体的是哪一台设备那么就可以使用蓝牙外设的UUIDString(peripheral.identifier.UUIDString)来作为唯一标识。但是需要注意的一点不同的中心设备也可以说是不同的手机对于同一台蓝牙设备获取到的UUIDString是不一样的。举例说明一下对于同一台蓝牙设备我的手机进行扫描然后读取它的UUIDString,和你的手机进行扫描获取到的UUIDString是不同的。


除了这些,在实际测试中,出现了这种情况:一些型号的按摩椅带有蓝牙音箱,因此,想要通过按摩椅播放手机中的歌曲,又要通过手机 APP 控制按摩椅需要进行两次蓝牙连接.首先在 APP 中发现蓝牙设备并连接,这里是对按摩椅进行控制;其次在设置中发现设备并连接,这里相当于连接了蓝牙音箱,连接后在设置中会出现两个蓝牙名称,如下图:




关于这一点,客户在进行测试的时候提出了这样一个问题:为什么需要两次蓝牙连接?而不是通过 APP 直接完成两次连接要达到的目的? 最佳合理的解释是: Apple 并没有赋予 CoreBluetooth 的接口最高的权限,所以通过 APP 进行的连接只可与蓝牙设备进行串口通信,而想要实现最高权限所能完成的功能,还是要依靠 iOS 系统的!


附: demo 主要代码

//
//  ViewController.m
//  BLE_DEMO
//
//  Created by 张闯 on 17/5/10.
//  Copyright © 2017年 张闯. All rights reserved.
//

#import "ViewController.h"
#import "DeviceViewController.h"


@interface ViewController () <UITableViewDelegate, UITableViewDataSource>

@property (nonatomic, strong)  BabyBluetooth       *baby;
@property (nonatomic, strong)  UITableView         *tableView;
@property (nonatomic, strong)  NSMutableArray      *dataSource;

@end

@implementation ViewController

- (void)viewWillAppear:(BOOL)animated 
    [super viewWillAppear:animated];
    [self.baby cancelAllPeripheralsConnection];


- (void)viewDidLoad 
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.navigationItem.title = @"设备列表";
    
    self.tableView = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
    [self.view addSubview:_tableView];
    
    self.dataSource = [NSMutableArray array];
    //初始化
    self.baby = [BabyBluetooth shareBabyBluetooth];
    //设置蓝牙代理方法
    [self babyDelegate];
    //扫描外设
    self.baby.scanForPeripherals().begin();
    
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(refreshBLE)];


- (void)refreshBLE 
    [self.dataSource removeAllObjects];
    [self.baby cancelAllPeripheralsConnection];
    self.baby.scanForPeripherals().begin();



//设置蓝牙委托
- (void)babyDelegate
    
    //设置扫描到设备的委托
    __weak typeof(self) weakSelf = self;
    [weakSelf.baby setBlockOnDiscoverToPeripherals:^(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI) 
        __strong typeof(self) strongSelf = self;
        
        if (![strongSelf.dataSource containsObject:peripheral]) 
            [strongSelf.dataSource addObject:peripheral];
            [self.tableView reloadData];
        
        
    ];
    
    //过滤器
    //设置查找设备的过滤器
    [weakSelf.baby setFilterOnDiscoverPeripherals:^BOOL(NSString *peripheralName, NSDictionary *advertisementData, NSNumber *RSSI) 
        if (peripheralName.length >1) 
            return YES;
        
        return NO;
    ];
    
    
    //设备状态改变
    [weakSelf.baby setBlockOnCentralManagerDidUpdateState:^(CBCentralManager *central) 
        
        switch (central.state) 
            case CBManagerStatePoweredOn:
                NSLog(@"CBManagerStatePoweredOn");
                [SVProgressHUD showSuccessWithStatus:@"蓝牙已打开"];
                break;
            case CBManagerStatePoweredOff:
                NSLog(@"CBManagerStatePoweredOff");
                [SVProgressHUD showErrorWithStatus:@"蓝牙已关闭"];
                break;
            case CBManagerStateResetting:
                NSLog(@"CBManagerStateResetting");
                break;
                
            case CBManagerStateUnsupported:
                NSLog(@"CBManagerStateUnsupported");
                break;
                
            case CBManagerStateUnauthorized:
                NSLog(@"CBManagerStateUnauthorized");
                break;
                
            case CBManagerStateUnknown:
                NSLog(@"CBManagerStateUnknown");
                break;
        
    ];
    


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
    return _dataSource.count;


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    cell.textLabel.text = [_dataSource[indexPath.row] name];
    return cell;



- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 
    
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    DeviceViewController *deviceVC = [[DeviceViewController alloc] init];
    deviceVC.peripheral = _dataSource[indexPath.row];
    [self.navigationController pushViewController:deviceVC animated:YES];



- (void)didReceiveMemoryWarning 
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.



@end

//
//  DeviceViewController.m
//  BLE_DEMO
//
//  Created by 张闯 on 17/5/11.
//  Copyright © 2017年 张闯. All rights reserved.
//

#import "DeviceViewController.h"

#define SERVICE_UUID            @"XXXX"
#define CHARACTERISTIC_TX       @"XXXX"
#define CHARACTERISTIC_RX       @"XXXX"

@interface DeviceViewController ()

@property (nonatomic, strong) BabyBluetooth         *baby;
@property (nonatomic, strong) CBCharacteristic      *characteristicR_x;
@property (nonatomic, strong) CBCharacteristic      *characteristicT_x;
@property (nonatomic, strong) NSArray               *autoTypeArray;
@property (nonatomic, assign) BOOL                  isConnected;
@property (weak,   nonatomic) IBOutlet UILabel      *receivedDataLabel;

@end

@implementation DeviceViewController



- (NSArray *)autoTypeArray 
    if (!_autoTypeArray) 
        _autoTypeArray = @[@"Wake", @"Energize", @"Perform", @"Recover", @"Upper", @"Lower"];
    
    return _autoTypeArray;


- (void)viewDidLoad 
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    

    self.baby = [BabyBluetooth shareBabyBluetooth];
    [self babyDelegate];
    
    //连接外设
    self.baby.having(self.peripheral).connectToPeripherals().begin();
    self.baby.characteristicDetails(self.peripheral,self.characteristicR_x);
    



//设置蓝牙委托
- (void)babyDelegate 
    
     __weak typeof(self) weakSelf = self;
    //连接Peripherals成功的委托
    [weakSelf.baby setBlockOnConnected:^(CBCentralManager *central, CBPeripheral *peripheral) 
        NSLog(@"已连接的设备:____%@", peripheral);
        //搜索指定服务
        [peripheral discoverServices: @[[CBUUID UUIDWithString:SERVICE_UUID]]];
        self.isConnected = YES;
        
    ];
    
    //查找服务
    [weakSelf.baby setBlockOnDiscoverServices:^(CBPeripheral *peripheral, NSError *error) 
        
        for (CBService *service in peripheral.services) 
            if([service.UUID isEqual:[CBUUID UUIDWithString:SERVICE_UUID]])
                [peripheral discoverCharacteristics:nil forService:service];
            
        
    ];
    
    //设置获取到最新Characteristics值的block
    [weakSelf.baby setBlockOnReadValueForCharacteristic:^(CBPeripheral *peripheral, CBCharacteristic *characteristic, NSError *error) 
        char rData[999];
        int len = (int)characteristic.value.length;
        [characteristic.value getBytes:rData length:len];
        NSData *d = [[NSData alloc] initWithBytes:rData length:len];
        
        if (d.length == 19) 
            self.receivedDataLabel.text = [self transferData:d];
        
    ];
    
    
    //查找到Characteristics的block
    [weakSelf.baby setBlockOnDiscoverCharacteristics:^(CBPeripheral *peripheral, CBService *service, NSError *error) 
        
        for (CBCharacteristic *c in service.characteristics) 
            if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_RX]]) 
                NSLog(@"CHARACTERISTIC_RX:_____%@", c);
                self.characteristicR_x = c;
                if (c.isNotifying) 
                    [self.baby cancelNotify:peripheral characteristic:c];
                 else 
                    [self.peripheral setNotifyValue:YES forCharacteristic:c];
                
            
            
            if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_TX]]) 
                
                self.characteristicT_x = c;
                NSLog(@"CHARACTERISTIC_TX:_____%@", c);
                if (c.isNotifying) 
                    [self.baby cancelNotify:peripheral characteristic:c];
                 else 
                    [self.peripheral setNotifyValue:YES forCharacteristic:c];
                
            
        
    ];
    
    
    //characteristic订阅状态改变的block
    [weakSelf.baby setBlockOnDidUpdateNotificationStateForCharacteristic:^(CBCharacteristic *characteristic, NSError *error) 
        NSLog(@"uid:%@,isNotifying:%@",characteristic.UUID,characteristic.isNotifying?@"on":@"off");
    ];
    
    //写Characteristic成功后的block
    [weakSelf.baby setBlockOnDidWriteValueForCharacteristic:^(CBCharacteristic *characteristic, NSError *error) 
        NSLog(@"t_xCBCharacteristic: ____ %@", characteristic);
    ];

    
    //断开Peripherals的连接
    [weakSelf.baby setBlockOnDisconnect:^(CBCentralManager *central, CBPeripheral *peripheral, NSError *error) 
        NSLog(@"%@", peripheral);
        
        if ([peripheral isEqual:self.peripheral]) 
            [SVProgressHUD showErrorWithStatus:@"断开链接了"];
            self.isConnected = NO;
        

    ];
    
    


- (IBAction)send:(id)sender 
    [self writeData:0x01];


- (IBAction)auto1:(id)sender 
    [self writeData:0x10];


- (IBAction)auto2:(id)sender 
    [self writeData:0x11];


- (IBAction)auto3:(id)sender 
    [self writeData:0x12];

- (IBAction)auto4:(id)sender 
    [self writeData:0x13];
    

- (IBAction)auto5:(id)sender 
    [self writeData:0x14];

- (IBAction)auto6:(id)sender 
    [self writeData:0x15];


- (void)writeData:(int)senderId 
    
    NSMutableData *data = [NSMutableData data];
    uint8_t tmp[10];
    uint8_t checksum = 0;
    tmp[0] = 0xF0;
    tmp[1] = 0x03;
    tmp[2] = senderId;
    checksum = tmp[1] + tmp[2];
    checksum = ~checksum;
    checksum = checksum & 0x7f;
    tmp[3] = checksum;
    tmp[4] = 0xF1;
    [data appendBytes:(void *)(&tmp) length:5];
    
    //写入数据
    if (self.isConnected) 
        [self.peripheral writeValue:data forCharacteristic:self.characteristicT_x type:CBCharacteristicWriteWithResponse];
     else 
        [SVProgressHUD showErrorWithStatus:@"连接已断开"];
    
    
    


- (NSString *)transferData:(NSData *)data 
    
    uint8_t *readTempBuffer = (uint8_t*)[data bytes];

    //运行状态
    int nChairRunState = (readTempBuffer[7]) & 0xf;
    //手动判断
    int nChairAutoType = (readTempBuffer[16] >> 2) & 0x0f;
    
    if (nChairAutoType && nChairRunState == 3) 
        return self.autoTypeArray[nChairAutoType-1];
     else 
        return @"none";
    
    




- (void)didReceiveMemoryWarning 
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.


/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.

*/

@end



以上是关于iOS 蓝牙BLE开发的主要内容,如果未能解决你的问题,请参考以下文章

IOS 蓝牙 BLE 写入值返回“未知错误”

Uni-App开发BLE低功耗蓝牙流程

iOS蓝牙(BLE4.0低功耗)详细渗透讲解

iOS BLE 模块开发总结

Android 低功耗Ble 蓝牙4.0多连接 开源框架

Android BLE低功耗蓝牙开发极简系列(二)之读写操作