步骤:

  1. 在info.plist中加入蓝牙的权限 NSBluetoothAlwaysUsageDescription :
  2. 创建蓝牙管理者对象,创建后,首先会执行系统蓝牙是否打开的协议方法centralManagerDidUpdateState,如果系统蓝牙未打开,会有系统的弹框提示打开蓝牙,如下:
  3. 打开系统蓝牙后,开始扫描设备,扫描到设备后会执行didDiscoverPeripheral这个发现外设的方法;在这里判断是自己需要连接的设备后,停止设备扫描并进行外设的连接,如下:
  4. 外设连接成功后,会执行didConnectPeripheral方法,在该方法中设置外设的代理并设置发现服务的方法:
  5. 发现服务之后会执行didDiscoverServices方法,这里可能会有多个服务,针对自己要进行读写的服务进行判断,当是自己要进行操作的服务后,执行发现特征的方法:
  6. 发现特征后会执行didUpdateValueForCharacteristic方法,有特征的结果如下:
  7. 当该特征是可读的特征时,即可进行读操作,成功之后会走didUpdateValueForCharcteristic方法,如下:
  8. 当该特征是可写的时候,写入成功会执行didWriteValueForCharcteristic方法,如下:
  9. 可以主动断开设备的连接,如下:

源码如下:

  1. EasyWriteViewController.h 这里用到了:pod 'QMUIKit'
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface EasyWriteViewController : QMUICommonViewController

@end

NS_ASSUME_NONNULL_END
  1. EasyWriteViewController.m
#import "EasyWriteViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>

#define kPeripheralName            @"Hi-PMA58-12HOL22"//硬件设备蓝牙名称
#define kServiceUUID               @"0000FFE0-3C17-D293-8E48-14FE2E4DA212" //服务的UUID
#define kWriteCharacteristicUUID   @"FFE1" //特征的UUID
#define kNotifyCharacteristicUUID  @"FFE2" //特征的UUID
#define kReadCharacteristicUUID    @"FFE3" //特征的UUID

// 输出语句
#ifdef DEBUG
#define DCLog(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])
#else
#define DCLog(...) do { } while (0)
#endif

@interface EasyWriteViewController ()<CBCentralManagerDelegate, CBPeripheralDelegate>

@property (nonatomic, strong) CBCentralManager *manager;
@property (nonatomic, strong) CBPeripheral *peripheral;
@property (nonatomic, strong) CBCharacteristic *character;
@property (nonatomic, strong) CBCharacteristic *characterRead;
@property (nonatomic, strong) CBCharacteristic *characterNofity;

@end

@implementation EasyWriteViewController

- (void)setupNavigationItems {
    [super setupNavigationItems];
    self.title = @"蓝牙数据读写";
    self.navigationItem.rightBarButtonItem = [UIBarButtonItem qmui_itemWithTitle:@"断开" target:self action:@selector(disConnectClick)];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建蓝牙管理者对象  第二个参数:nil默认为主线程
    self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
    
    QMUIButton *blueWriteBtn = [DCUIHelper generateDarkFilledButton];
    blueWriteBtn.frame = CGRectMake(20, DC_STATUS_NAV_HEIGHT + 10, SCREEN_WIDTH - 40, 40);
    [blueWriteBtn setTitle:@"蓝牙写数据" forState:UIControlStateNormal];
    blueWriteBtn.tag = 1;
    [blueWriteBtn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:blueWriteBtn];
    
    QMUIButton *blueReadBtn = [DCUIHelper generateDarkFilledButton];
    blueReadBtn.frame = CGRectMake(20, DC_STATUS_NAV_HEIGHT + 60, SCREEN_WIDTH - 40, 40);
    [blueReadBtn setTitle:@"蓝牙读数据" forState:UIControlStateNormal];
    blueReadBtn.tag = 2;
    [blueReadBtn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:blueReadBtn];
    
    QMUIButton *blueNotifyBtn = [DCUIHelper generateDarkFilledButton];
    blueNotifyBtn.frame = CGRectMake(20, DC_STATUS_NAV_HEIGHT + 110, SCREEN_WIDTH - 40, 40);
    [blueNotifyBtn setTitle:@"蓝牙通知数据" forState:UIControlStateNormal];
    blueNotifyBtn.tag = 3;
    [blueNotifyBtn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:blueNotifyBtn];
}

- (void)btnClick:(UIButton *)btn {
    switch (btn.tag) {
        case 1: {
            [_peripheral readRSSI];
            [self writeDataToBle:@"ling123"];
        }
            break;
            
        case 2: {
            if (_characterRead.properties & CBCharacteristicPropertyRead) {
                [_peripheral readValueForCharacteristic:_characterRead];
            } else {
                DCLog(@"该字段不可读");
            }
        }
            break;
            
        case 3: {
            if (_characterNofity.properties & CBCharacteristicPropertyNotify) {
                [_peripheral setNotifyValue:YES forCharacteristic:_characterNofity];
            } else {
                DCLog(@"该字段不可通知");
            }
        }
            break;
    }
}

- (void)disConnectClick {
    [_manager cancelPeripheralConnection:self.peripheral]; // 主动断开连接
}

#pragma mark - 定时器时间事件
- (void)timered:(NSTimer *)timer {
    // 读信号强度
    [_peripheral readRSSI];
    // 实时读数
//    [self writeDataToBle:@"*APP.VALUE#$"];
    [self writeDataToBle:@"ling123"];
}

// 向蓝牙写入数
- (void)writeDataToBle:(NSString *)aStr {
    if (_character.properties & CBCharacteristicPropertyWrite) {
        // BLE设备接收的数据需要转为NSData类型
        NSData *data = [aStr dataUsingEncoding:NSUTF8StringEncoding];
        // 第一个参数:你要写入的数据,为data类型。
        // 第二个参数:你要向哪个特征写入,一般就是属性为写入的那个特征。
        // 第三个参数:你写入的方式,这个参数有两个枚举分别是:
        // CBCharacteristicWriteWithResponse//写入之后有回应
        // CBCharacteristicWriteWithoutResponse//写入之后不需要回应
        // 使用WithResponse的成功后就会调用:-(void)peripheral:(CBPeripheral )peripheral didWriteValueForCharacteristic:(CBCharacteristic )characteristic error:(NSError )error;
        [_peripheral writeValue:data forCharacteristic:_character type:CBCharacteristicWriteWithResponse];
    } else {
        DCLog(@"该字段不可写!");
    }
}

- (void)scanBlueTooth {
//    [self.manager scanForPeripheralsWithServices:nil options:nil]; // 扫描所有蓝牙设备
    [self.manager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}]; // 搜索附近设备
//    [self.manager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:@"D3A97ECF-C939-C85C-9923-BA2E9684E6B6"]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}]; // 扫描具体蓝牙设备
}

#pragma mark - CBCentralManagerDelegate
// 该方法当蓝牙状态改变(打开或者关闭)的时候就会调用
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    switch (central.state) {
        case CBManagerStateUnknown: {
            DCLog(@"未知蓝牙状态");
        } break;
        case CBManagerStateResetting: {
            DCLog(@"系统服务连接暂时丢失");
        } break;
        case CBManagerStateUnsupported: {
            DCLog(@"该设备不支持蓝牙");
        } break;
        case CBManagerStateUnauthorized: {
            DCLog(@"该设备蓝牙为被授权");
        } break;
        case CBManagerStatePoweredOn: {
            DCLog(@"该设备蓝牙已打开");
            [QMUITips showSucceed:@"该设备蓝牙已打开"];
            // 打开后,开始扫描
            [self scanBlueTooth];
        } break;
        case CBManagerStatePoweredOff: {
            DCLog(@"该设备蓝牙没有打开");
        } break;
    }
}

/**
 发现外设后调用的方法 查到外设后,连接设备,停止扫描
 param:peripheral 外设(即蓝牙设备),连接,写入数据的时候需要用到。
 param:advertisementData  蓝牙传输过来的数据,包括:设备名称,自定义数据等。    自定义数据有长度限制,自己想要什么数据要和嵌入式商量好,例如MAC地址,传感器数据等
 param:RSSI  侧面反映距离的远近
 */
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI {
    // 可在该方法内部区分扫描到的蓝牙设备
    DCLog(@"发现外设peripheral:%@ =====RSSI:%@ =====UUID:%@", peripheral, RSSI, peripheral.identifier);
    // 判断是否为你要连接的设备(我这里用设备名称判断的,扫描到我要的外设蓝牙名称后,停止扫描、连接外设)
    if ([peripheral.name isEqualToString:kPeripheralName]) {
        // 扫描到设备之后停止扫描
        [_manager stopScan];
        // 开始连接外设
        [_manager connectPeripheral:peripheral options:nil];
        _peripheral = peripheral;
    }
}

// 连接失败会被调用
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    DCLog(@"=====>连接失败");
}

// 断开连接会被调用
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    DCLog(@"=====>断开连接");
    // 断开连接后,可能是因为信号不好,所以我们的需求是继续连接我们的设备
    [QMUITips showSucceed:@"设备已断开"];
//    [self scanBlueTooth];
}

// 连接成功会被调用
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    DCLog(@"=====>%@连接成功 =====>UUID: %@", peripheral.name, peripheral.identifier);
    
    [QMUITips showSucceed:@"设备连接成功"];
    
    // 连接设备之后设置蓝牙对象的代理,扫描服务
    [self.peripheral setDelegate:self];

    // 外设发现服务,传nil代表不过滤
    // 这里会触发外设的代理 didDiscoverServices 方法
    [self.peripheral discoverServices:nil];

    // 这是我们业务需求:0.2s给我们的采集卡设备发一次命令来读数
//    [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(timered:) userInfo:nil repeats:YES];
}

#pragma mark - CBPeripheralDelegate
// 返回的蓝牙服务通知通过代理实现(已经发现服务)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    DCLog(@"=====>发现服务");
    // 一个设备可能为多个服务,所以要取你需要读写的那个服务(我们的外设就一个服务)
    for (CBService *service in peripheral.services) {
        if ([service.UUID isEqual:[CBUUID UUIDWithString:kServiceUUID]]) {
            // 根据你要的那个服务去发现特性
            [self.peripheral discoverCharacteristics:nil forService:service];
        }
    }
}

// 读取每个特征值 获得外围设备的服务、获得服务的特征
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
    // 首先读到外设特征服务,写到宏定义
    DCLog(@"发现特征服务:%@", service.UUID);

    for (CBCharacteristic *c in service.characteristics) {
        // 其次读到两个特征值,写到宏定义
        DCLog(@"特征UUID: %@", c.UUID);

        // 哪个特征值是读的,哪个特征值是写的,一般硬件兄弟都会告诉你的
        // (即使没有告诉你,也可以自己在AppStore下载LightBlue或UsrBleAssistent蓝牙调试工具看到你需要读写的特征值)
        if ([c.UUID.UUIDString isEqualToString:kWriteCharacteristicUUID]) {
            // 找到可写特征值D2
            _character = c;

        } else if ([c.UUID.UUIDString isEqualToString:kNotifyCharacteristicUUID]) {
            // 设置通知
            _characterNofity = c;
//            [_peripheral setNotifyValue:YES forCharacteristic:c];
//            [QMUITips showSucceed:@"设置通知"];
            
        } else if ([c.UUID.UUIDString isEqualToString:kReadCharacteristicUUID]) {
            _characterRead = c;
//            [_peripheral readValueForCharacteristic:c];
//            [QMUITips showSucceed:@"读取数据"];
        }
    }
}

// 读取指定特征的特征值 获取外设发来的数据,不论是read和notify,获取数据都是从这个方法中读取
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
    if (!error) {
//        if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kNotifyCharacteristicUUID]]) {
            // 此处的byte数组就是接收到的数据
            NSString *str = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
            DCLog(@"原始数据: %@ ===> 字符串: %@ ===>uuid: %@", characteristic.value, str, characteristic.UUID);
//        }
    } else {
        DCLog(@"error:%@", error);
    }
}

// 写入数据成功后回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error {
    DCLog(@"characteristic===>%@  value===>%@",characteristic, characteristic.value);
}

- (void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(nullable NSError *)error {
    DCLog(@"peripheral==%@ RSSI==%@ error==%@",peripheral, RSSI, error);
}

@end