开源 Objective-C IOS 应用开发(十五)通讯--蓝牙ble扫描

  文章的目的为了记录使用Objective-C 进行IOS app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

 相关链接:

开源 Objective-C IOS 应用开发(一)macOS 的使用

开源 Objective-C IOS 应用开发(二)Xcode安装

开源 Objective-C IOS 应用开发(三)第一个iPhone的APP

开源 Objective-C IOS 应用开发(四)Xcode工程文件结构

开源 Objective-C IOS 应用开发(五)iOS操作(action)和输出口(Outlet)

开源 Objective-C IOS 应用开发(六)Objective-C 和 C语言

开源 Objective-C IOS 应用开发(七)Objective-C核心代码示例

开源 Objective-C IOS 应用开发(八)常见控件UI

开源 Objective-C IOS 应用开发(九)复杂控件-tableview

开源 Objective-C IOS 应用开发(十)数据持久化--文件

开源 Objective-C IOS 应用开发(十一)数据持久化--sqlite

开源 Objective-C IOS 应用开发(十二)通讯--ble

开源 Objective-C IOS 应用开发(十三)通讯--Http访问

开源 Objective-C IOS 应用开发(十四)传感器--陀螺仪和gps

开源 Objective-C IOS 应用开发(十五)通讯--蓝牙ble扫描

开源 Objective-C IOS 应用开发(十六)Storyboard模式下的纯代码界面

开源 Objective-C IOS 应用开发(十七)CAF音频的录制

开源 Objective-C IOS 应用开发(十八)音频的播放

开源 Objective-C IOS 应用开发(十九)视频的播放

开源 Objective-C IOS 应用开发(二十)多线程处理

开源 Objective-C IOS 应用开发(二十一)自定义控件--示波器

开源 Objective-C IOS 应用开发(二十二)自定义控件--车速仪表盘

 推荐链接:

开源 Arkts 鸿蒙应用 开发(一)工程文件分析-CSDN博客

开源 Arkts 鸿蒙应用 开发(二)封装库.har制作和应用-CSDN博客

开源 Arkts 鸿蒙应用 开发(三)Arkts的介绍-CSDN博客

开源 Arkts 鸿蒙应用 开发(四)布局和常用控件-CSDN博客

开源 Arkts 鸿蒙应用 开发(五)控件组成和复杂控件-CSDN博客

开源 Arkts 鸿蒙应用 开发(六)数据持久--文件和首选项存储-CSDN博客

开源 Arkts 鸿蒙应用 开发(七)数据持久--sqlite关系数据库-CSDN博客

开源 Arkts 鸿蒙应用 开发(八)多媒体--相册和相机-CSDN博客

开源 Arkts 鸿蒙应用 开发(九)通讯--tcp客户端-CSDN博客

开源 Arkts 鸿蒙应用 开发(十)通讯--Http-CSDN博客

开源 Arkts 鸿蒙应用 开发(十一)证书和包名修改-CSDN博客

开源 Arkts 鸿蒙应用 开发(十二)传感器的使用-CSDN博客

开源 Arkts 鸿蒙应用 开发(十三)音频--MP3播放_arkts avplayer播放音频 mp3-CSDN博客

开源 Arkts 鸿蒙应用 开发(十四)线程--任务池(taskpool)-CSDN博客

开源 Arkts 鸿蒙应用 开发(十五)自定义绘图控件--仪表盘-CSDN博客

开源 Arkts 鸿蒙应用 开发(十六)自定义绘图控件--波形图-CSDN博客

开源 Arkts 鸿蒙应用 开发(十七)通讯--http多文件下载-CSDN博客

开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器-CSDN博客

推荐链接:

开源 java android app 开发(一)开发环境的搭建-CSDN博客

开源 java android app 开发(二)工程文件结构-CSDN博客

开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客

开源 java android app 开发(四)GUI界面重要组件-CSDN博客

开源 java android app 开发(五)文件和数据库存储-CSDN博客

开源 java android app 开发(六)多媒体使用-CSDN博客

开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客

开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客

开源 java android app 开发(九)后台之线程和服务-CSDN博客

开源 java android app 开发(十)广播机制-CSDN博客

开源 java android app 开发(十一)调试、发布-CSDN博客

开源 java android app 开发(十二)封库.aar-CSDN博客
 

本章内容主要使用iphone手机对蓝牙ble设备进行扫描。

目录:

1.手机演示

2.所有源码

3.源码分析

一、手机演示

二、所有源码

使用storyboard创建以下界面

在Storyboard中需要添加以下UI组件:

  1. 添加两个按钮:

    • "开始扫描"按钮 - 连接到 startScanButton 和 startScanButtonTapped: 方法

    • "清空日志"按钮 - 连接到 clearLogButton 和 clearLogButtonTapped: 方法

  2. 添加一个TextView:

    • 连接到 logTextView

ViewController.h文件

#import <UIKit/UIKit.h>
#import <CoreBluetooth/CoreBluetooth.h>

@interface ViewController : UIViewController <CBCentralManagerDelegate, CBPeripheralDelegate>

@property (strong, nonatomic) CBCentralManager *centralManager;
@property (strong, nonatomic) NSMutableArray *discoveredPeripherals;
@property (strong, nonatomic) NSTimer *scanTimer;

// UI ***ponents - 这些将在Storyboard中连接
@property (weak, nonatomic) IBOutlet UITextView *logTextView;

@property (weak, nonatomic) IBOutlet UIButton *startScanButton;
@property (weak, nonatomic) IBOutlet UIButton *clearLogButton;



@end

ViewController.m文件

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupUI];
    [self initializeBLE];
}

- (void)setupUI {
    // 设置按钮样式
    self.startScanButton.backgroundColor = [UIColor systemBlueColor];
    self.startScanButton.tintColor = [UIColor whiteColor];
    self.startScanButton.layer.cornerRadius = 8;
    
    self.clearLogButton.backgroundColor = [UIColor systemGrayColor];
    self.clearLogButton.tintColor = [UIColor whiteColor];
    self.clearLogButton.layer.cornerRadius = 8;
    
    // 设置文本框样式
    self.logTextView.editable = NO;
    self.logTextView.scrollEnabled = YES;
    self.logTextView.font = [UIFont systemFontOfSize:12];
    self.logTextView.backgroundColor = [UIColor blackColor];
    self.logTextView.textColor = [UIColor greenColor];
    self.logTextView.layer.cornerRadius = 8;
    self.logTextView.layer.borderWidth = 1;
    self.logTextView.layer.borderColor = [UIColor grayColor].CGColor;
}

- (void)initializeBLE {
    self.discoveredPeripherals = [NSMutableArray array];
    // 在后台队列中初始化CBCentralManager
    dispatch_queue_t centralQueue = dispatch_queue_create("***.ble.scan.queue", DISPATCH_QUEUE_SERIAL);
    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue options:@{CBCentralManagerOptionShowPowerAlertKey: @YES}];
}

#pragma mark - Button Actions

- (IBAction)startScanButtonTapped:(id)sender {
    [self startScanning];
}

- (IBAction)clearLogButtonTapped:(id)sender {
    [self clearLog];
}

- (void)startScanning {
    if (self.centralManager.state != CBManagerStatePoweredOn) {
        [self appendToLog:@"蓝牙未开启,请检查蓝牙状态"];
        return;
    }
    
    [self appendToLog:@"开始扫描BLE设备..."];
    [self.startScanButton setTitle:@"扫描中..." forState:UIControlStateNormal];
    self.startScanButton.enabled = NO;
    
    // 清空之前发现的设备
    [self.discoveredPeripherals removeAllObjects];
    
    // 开始扫描
    NSDictionary *scanOptions = @{CBCentralManagerScanOptionAllowDuplicatesKey: @YES};
    [self.centralManager scanForPeripheralsWithServices:nil options:scanOptions];
    
    // 设置10秒后停止扫描
    self.scanTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:@selector(stopScanning) userInfo:nil repeats:NO];
}

- (void)stopScanning {
    [self.centralManager stopScan];
    [self appendToLog:@"扫描结束"];
    
    // 在主线程更新UI
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.startScanButton setTitle:@"开始扫描" forState:UIControlStateNormal];
        self.startScanButton.enabled = YES;
    });
    
    if (self.scanTimer) {
        [self.scanTimer invalidate];
        self.scanTimer = nil;
    }
}

- (void)clearLog {
    self.logTextView.text = @"";
    [self appendToLog:@"日志已清空"];
}

#pragma mark - CBCentralManagerDelegate

- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    NSString *stateString;
    switch (central.state) {
        case CBManagerStatePoweredOn:
            stateString = @"蓝牙已开启";
            break;
        case CBManagerStatePoweredOff:
            stateString = @"蓝牙已关闭";
            break;
        case CBManagerStateResetting:
            stateString = @"蓝牙重置中";
            break;
        case CBManagerStateUnauthorized:
            stateString = @"蓝牙未授权";
            break;
        case CBManagerStateUnknown:
            stateString = @"蓝牙状态未知";
            break;
        case CBManagerStateUnsupported:
            stateString = @"设备不支持蓝牙";
            break;
        default:
            stateString = @"蓝牙状态异常";
            break;
    }
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self appendToLog:[NSString stringWithFormat:@"蓝牙状态: %@", stateString]];
    });
}

- (void)centralManager:(CBCentralManager *)central
 didDiscoverPeripheral:(CBPeripheral *)peripheral
     advertisementData:(NSDictionary<NSString *,id> *)advertisementData
                  RSSI:(NSNumber *)RSSI {
    
    // 防止重复添加同一设备
    if (![self.discoveredPeripherals containsObject:peripheral]) {
        [self.discoveredPeripherals addObject:peripheral];
    }
    
    // 获取设备信息
    NSString *deviceName = peripheral.name ?: @"Unknown";
    NSString *macAddress = [self getMacAddressFromAdvertisementData:advertisementData];
    NSString *rssiString = [RSSI stringValue];
    
    // 解析广播数据
    NSString *adDataString = [self parseAdvertisementData:advertisementData];
    
    // 在主线程更新UI
    dispatch_async(dispatch_get_main_queue(), ^{
        NSString *logEntry = [NSString stringWithFormat:
                             @"设备名: %@\n"
                             @"MAC地址: %@\n"
                             @"RSSI: %@ dBm\n"
                             @"广播数据: %@\n"
                             @"UUID: %@\n"
                             @"----------------------------------------\n",
                             deviceName, macAddress, rssiString, adDataString, peripheral.identifier.UUIDString];
        
        [self appendToLog:logEntry];
    });
}

#pragma mark - Helper Methods

- (NSString *)getMacAddressFromAdvertisementData:(NSDictionary *)advertisementData {
    // 在iOS中,出于隐私考虑,无法直接获取真实的MAC地址
    // 这里我们使用设备的UUID作为标识符
    return @"N/A (iOS隐私限制)";
}

- (NSString *)parseAdvertisementData:(NSDictionary *)advertisementData {
    NSMutableString *result = [NSMutableString string];
    
    // 解析服务UUID
    NSArray *serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey];
    if (serviceUUIDs && serviceUUIDs.count > 0) {
        [result appendString:@"服务UUID: "];
        for (CBUUID *uuid in serviceUUIDs) {
            [result appendFormat:@"%@ ", uuid.UUIDString];
        }
        [result appendString:@"\n"];
    }
    
    // 解析本地名称
    NSString *localName = advertisementData[CBAdvertisementDataLocalNameKey];
    if (localName) {
        [result appendFormat:@"本地名称: %@\n", localName];
    }
    
    // 解析制造商数据
    NSData *manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey];
    if (manufacturerData) {
        [result appendFormat:@"制造商数据: %@\n", [self hexStringFromData:manufacturerData]];
    }
    
    // 解析服务数据
    NSDictionary *serviceData = advertisementData[CBAdvertisementDataServiceDataKey];
    if (serviceData) {
        [result appendString:@"服务数据: "];
        for (CBUUID *uuid in serviceData) {
            NSData *data = serviceData[uuid];
            [result appendFormat:@"%@: %@ ", uuid.UUIDString, [self hexStringFromData:data]];
        }
        [result appendString:@"\n"];
    }
    
    // 解析TX功率级别
    NSNumber *txPower = advertisementData[CBAdvertisementDataTxPowerLevelKey];
    if (txPower) {
        [result appendFormat:@"TX功率: %@ dBm\n", txPower];
    }
    
    if (result.length == 0) {
        [result appendString:@"无广播数据"];
    }
    
    return [result copy];
}

- (NSString *)hexStringFromData:(NSData *)data {
    if (!data) return @"";
    
    const unsigned char *dataBuffer = (const unsigned char *)[data bytes];
    if (!dataBuffer) return @"";
    
    NSUInteger dataLength = [data length];
    NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
    
    for (int i = 0; i < dataLength; ++i) {
        [hexString appendFormat:@"%02lX", (unsigned long)dataBuffer[i]];
    }
    
    return [NSString stringWithString:hexString];
}

- (void)appendToLog:(NSString *)message {
    NSString *timestamp = [self getCurrentTimestamp];
    NSString *logMessage = [NSString stringWithFormat:@"[%@] %@\n", timestamp, message];
    
    // 在主线程更新文本框
    dispatch_async(dispatch_get_main_queue(), ^{
        self.logTextView.text = [self.logTextView.text stringByAppendingString:logMessage];
        
        // 自动滚动到底部
        if (self.logTextView.text.length > 0) {
            NSRange bottom = NSMakeRange(self.logTextView.text.length - 1, 1);
            [self.logTextView scrollRangeToVisible:bottom];
        }
    });
}

- (NSString *)getCurrentTimestamp {
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"HH:mm:ss.SSS"];
    return [formatter stringFromDate:[NSDate date]];
}

- (void)dealloc {
    if (self.scanTimer) {
        [self.scanTimer invalidate];
        self.scanTimer = nil;
    }
}

@end

在 Info.plist 中添加蓝牙使用描述:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>应用需要蓝牙权限来扫描附近的BLE设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>应用需要蓝牙权限来扫描附近的BLE设备</string>

三、源码分析

1. 数据模型类

BLEDevice 类

@interface BLEDevice : NSObject
@property (strong, nonatomic) CBPeripheral *peripheral;
@property (strong, nonatomic) NSString *deviceIdentifier;
@property (strong, nonatomic) NSNumber *rssi;
@property (strong, nonatomic) NSDictionary *advertisementData;
@property (strong, nonatomic) NSDate *discoveryTime;
@end

功能:自定义数据模型,用于存储BLE设备的完整信息,实现设备数据的封装和管理。

2. 生命周期函数

viewDidLoad

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupUI];
    [self initializeBLE];
}

功能:视图控制器生命周期函数,在视图加载完成后调用,初始化UI和BLE模块。

dealloc

- (void)dealloc {
    if (self.scanTimer) {
        [self.scanTimer invalidate];
        self.scanTimer = nil;
    }
    if (self.deviceUpdateTimer) {
        [self.deviceUpdateTimer invalidate];
        self.deviceUpdateTimer = nil;
    }
}

功能:对象销毁时调用,清理定时器资源,防止内存泄漏。

3. 初始化函数

setupUI

- (void)setupUI {
    // 设置按钮样式和文本框样式
}

功能:配置用户界面元素的视觉样式,包括按钮颜色、圆角、文本框背景等。

initializeBLE

- (void)initializeBLE {
    self.discoveredDevices = [NSMutableDictionary dictionary];
}

功能:初始化BLE相关数据结构,创建用于存储已发现设备的字典。

4. 按钮动作函数

startScanButtonTapped:

- (IBAction)startScanButtonTapped:(id)sender {
    [self startScanning];
}

功能:开始扫描按钮的点击事件处理函数。

clearLogButtonTapped:

- (IBAction)clearLogButtonTapped:(id)sender {
    [self clearLog];
}

功能:清空日志按钮的点击事件处理函数。

5. BLE扫描控制函数

startScanning

- (void)startScanning {
    // 1. 检查蓝牙状态
    // 2. 清空之前发现的设备
    // 3. 开始BLE扫描
    // 4. 启动设备更新定时器
    // 5. 设置10秒后停止扫描的定时器
}

功能:启动BLE设备扫描流程,包含状态检查、数据清理、扫描启动和定时器设置。

stopScanning

- (void)stopScanning {
    // 1. 停止BLE扫描
    // 2. 停止设备更新定时器
    // 3. 更新UI状态
    // 4. 清理扫描定时器
}

功能:停止BLE扫描,清理相关资源,恢复UI状态。

6. CBCentralManagerDelegate 协议函数

centralManagerDidUpdateState:

- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
    // 处理蓝牙状态变化
    switch (central.state) {
        case CBManagerStatePoweredOn: ...
        // 其他状态处理
    }
}

功能:蓝牙状态变化的回调函数,处理蓝牙开启、关闭、未授权等各种状态。

centralManager:didDiscoverPeripheral:advertisementData:RSSI:

- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
     advertisementData:(NSDictionary *)advertisementData
                  RSSI:(NSNumber *)RSSI {
    // 设备发现逻辑:
    // 1. 使用UUID作为设备唯一标识
    // 2. 检查设备是否已存在
    // 3. 存在则更新数据,不存在则创建新记录
}

功能:核心的设备发现回调函数,处理新设备的发现和已存在设备的更新。

7. 设备显示管理函数

updateDeviceDisplay

- (void)updateDeviceDisplay {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self refreshDeviceListDisplay];
    });
}

功能:封装设备显示更新操作,确保在主线程执行UI更新。

refreshDeviceListDisplay

- (void)refreshDeviceListDisplay {
    // 1. 检查是否有设备
    // 2. 按设备名称排序
    // 3. 构建显示字符串
    // 4. 更新文本框内容
    // 5. 自动滚动到底部
}

功能:刷新设备列表显示,包括排序、格式化和UI更新。

8. 工具函数

clearLog

- (void)clearLog {
    self.logTextView.text = @"";
    [self appendToLog:@"日志已清空"];
}

功能:清空日志文本框内容。

appendToLog:

- (void)appendToLog:(NSString *)message {
    // 添加时间戳,在主线程更新文本框
}

功能:向日志文本框添加带时间戳的消息。

getMacAddressFromAdvertisementData:

- (NSString *)getMacAddressFromAdvertisementData:(NSDictionary *)advertisementData {
    return @"N/A (iOS隐私限制)";
}

功能:由于iOS隐私限制,无法获取真实MAC地址,返回提示信息。

parseAdvertisementData:

- (NSString *)parseAdvertisementData:(NSDictionary *)advertisementData {
    // 解析广播数据中的:
    // - 服务UUID
    // - 本地名称
    // - 制造商数据
    // - 服务数据
    // - TX功率级别
}

功能:解析BLE设备的广播数据,提取各种有用信息。

hexStringFromData:

- (NSString *)hexStringFromData:(NSData *)data {
    // 将NSData转换为十六进制字符串表示
}

功能:将二进制数据转换为可读的十六进制字符串。

getCurrentTimestamp

- (NSString *)getCurrentTimestamp {
    // 获取当前时间的格式化字符串
}

功能:生成格式化的时间戳字符串。

转载请说明出处内容投诉
CSS教程网 » 开源 Objective-C IOS 应用开发(十五)通讯--蓝牙ble扫描

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买