目前iOS智能硬件的开发交互方式主要分为两种,一种是基于低功耗的蓝牙4.0技术(由于耗电低,也称作为BLE(Bluetooth Low Energy))对应iOS的框架为CoreBluetooth,另外一种是基于Wi-Fi的连接方式,由于之前做过的两个项目用到的都是蓝牙,所以下面主要是介绍蓝牙4.0技术。

对应的在项目中添加的蓝牙开发权限

Important

An iOS app linked on or after iOS 10.0 must include in its Info.plist file the usage description
keys for the types of data it needs to access or it will crash. To access Bluetooth peripheral data specifically,
it must include NSBluetoothPeripheralUsageDescription.

在iOS中用于蓝牙开发的框架是CoreBluetooth,里面主要有以下几个核心概念:

CBCentralManager: 外部设备管理者

CBPeripheral: 连接的外部设备

CBService: 设备携带的服务

CBCharacteristic: 服务中包含的特征值

蓝牙的开发分两种模式,一种是app作为主设备,扫描连接其他外部蓝牙设备,另外一种是app作为外部设备被主设备连接。

目前开发的两个项目中主要用到的是第一种模式。

下面讲解一下第一种模式的流程:

1、首先先创建CBCentralManager对象,初始化完设置相应的代理对象之后就会有一个CBCentralManager的回调方法过来。

2、根据CBCentralManager初始化完之后的回调方法判断蓝牙目前的相应状态,如果处于开启状态则开始扫描包含你所需要的服务的外设。

-(void)centralManagerDidUpdateState:(CBCentralManager *)central{

    switch (central.state) {
case CBManagerStateUnknown:
NSLog(@"CBManagerStateUnknown");
break;
case CBManagerStateResetting:
NSLog(@"CBManagerStateResetting");
break;
case CBManagerStateUnsupported:
NSLog(@"CBManagerStateUnsupported");
break;
case CBManagerStateUnauthorized:
NSLog(@"CBManagerStateUnauthorized");
break;
case CBManagerStatePoweredOff:
NSLog(@"CBManagerStatePoweredOff");
break;
case CBManagerStatePoweredOn:
NSLog(@"CBManagerStatePoweredOn");
//开始扫描周围的外设
/*
第一个参数nil就是扫描周围所有的外设,扫描到外设后会进入
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;
第二个参数可以添加一些option,来增加精确的查找范围, 如 :
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey,
nil];
[manager scanForPeripheralsWithServices:nil options:options]; */
[central scanForPeripheralsWithServices:nil options:nil]; break;
default:
break;
} }

3、扫面发现需要的外围设备

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{

    //连接外围设备
   //advertisementData 外围设备广播的信息,一般会在kCBAdvDataLocalName这个地方放置设备的mac地址,如果安卓收得到iOS设备收不到,一般来说是因为硬件设备没有调试好,可以找硬件工程师师调试
  if ([peripheral.name isEqualToString:@"外设名字"] && peripheral) { [self.centralManager connectPeripheral:peripheral options:nil]; } }

4、连接外部设备

//连接到外围设备
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
NSLog(@"连接外围设备成功");
//设置外围设备的代理为当前视图控制器
peripheral.delegate = self;
//外围设备开始寻找服务
[peripheral discoverServices:@[[CBUUID UUIDWithString:kServiceUUID]]];
} //连接外围设备失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"连接外围设备失败!");
}

5、发现外部设备的服务、发现外部设备的服务对应的特征值

//外围设备寻找到服务后
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
NSLog(@"已发现可用服务...");
if (error) {
NSLog(@"外围设备寻找服务过程中发生错误,错误信息:%@",error.localizedDescription);
} //遍历查找到的服务
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
for (CBService *service in peripheral.services){
if ([service.UUID isEqual:serviceUUID]) {
//外围设备查找指定服务中的特征
[peripheral discoverCharacteristics:@[characteristicUUID] forService:service];
}
}
} //外围设备寻找到特征后
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
NSLog(@"已发现可用特征....");
if (error) {
NSLog(@"外围设备寻找特征过程中发生错误,错误信息:%@",error.localizedDescription);
} //遍历服务中的特征
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
if ([service.UUID isEqual:serviceUUID]) {
for (CBCharacteristic *characteristic in service.characteristics){
if ([characteristic.UUID isEqual:characteristicUUID]) {
//情景一:通知
/*找到特征后设置外围设备为已通知状态(订阅特征):
1.调用此方法会触发代理方法-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
2.调用此方法会触发外围设备的订阅代理方法
*/
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
//情景二:读取
// [peripheral readValueForCharacteristic:characteristic];
// if (characteristic.value) {
// NSString *value = [[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
// NSLog(@"读取到特征值:%@",value);
// }
}
}
}
}

6、订阅特征值的状态发生更新

//特征值被更新后
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSLog(@"收到特征更新通知...");
if (error) {
NSLog(@"更新通知状态时发生错误,错误信息:%@",error.localizedDescription);
} //给特征值设置新的值
CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
if ([characteristic.UUID isEqual:characteristicUUID]) {
if (characteristic.isNotifying) {
if (characteristic.properties == CBCharacteristicPropertyNotify) {
NSLog(@"已订阅特征通知.");
return;
}else if (characteristic.properties == CBCharacteristicPropertyRead){
//从外围设备读取新值,调用此方法会触发代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
[peripheral readValueForCharacteristic:characteristic];
}
}else{
NSLog(@"停止已停止."); //取消连接
[self.centralManager cancelPeripheralConnection:peripheral];
}
}
}

7、特征值发生变化时的回调方法

//更新特征值后(调用readValueForCharacteristic:方法或者外围设备在订阅后更新特征值都会调用此代理方法)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"更新特征值时发生错误,错误信息:%@",error.localizedDescription);
return;
}
if (characteristic.value) {
NSString *value = [[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
NSLog(@"读取到特征值:%@",value);
}else{
NSLog(@"未发现特征值.");
}
}

8、往蓝牙设备写入数据

- (void)writeDataWithHexStr:(NSString *)hexStr {
NSData *data = [self convertHexStrToData:hexStr];
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
} // 16进制转NSData
- (NSData *)convertHexStrToData:(NSString *)str
{
if (!str || [str length] == 0) {
return nil;
} NSMutableData *hexData = [[NSMutableData alloc] initWithCapacity:20];
NSRange range;
if ([str length] % 2 == 0) {
range = NSMakeRange(0, 2);
} else {
range = NSMakeRange(0, 1);
}
for (NSInteger i = range.location; i < [str length]; i += 2) {
unsigned int anInt;
NSString *hexCharStr = [str substringWithRange:range];
NSScanner *scanner = [[NSScanner alloc] initWithString:hexCharStr]; [scanner scanHexInt:&anInt];
NSData *entity = [[NSData alloc] initWithBytes:&anInt length:1];
[hexData appendData:entity]; range.location += range.length;
range.length = 2;
}
return hexData;
}

9、关闭蓝牙设备

- (void)closeBlueTooth {
[self.centralManager stopScan];
if (self.peripheral) {
[self.centralManager cancelPeripheralConnection:self.peripheral];
}
self.centralManager = nil;
self.peripheral = nil;
self.characteristic = nil;
}

10、对应的还有swift版蓝牙相关的常用工具类方法

//MARK: -  工具类方法
//16进制字符串转化为data
class func HexStrToData(hexStr:String)->Data { assert(hexStr.count % 2 == 0, "输入字符串格式不对,8位代表一个字符")
var bytes = [UInt8]()
var sum = 0
// 整形的 utf8 编码范围
let intRange = 48...57
// 小写 a~f 的 utf8 的编码范围
let lowercaseRange = 97...102
// 大写 A~F 的 utf8 的编码范围
let uppercasedRange = 65...70
for (index, c) in hexStr.utf8CString.enumerated() {
var intC = Int(c.byteSwapped)
if intC == 0 {
break
} else if intRange.contains(intC) {
intC -= 48
} else if lowercaseRange.contains(intC) {
intC -= 87
} else if uppercasedRange.contains(intC) {
intC -= 55
} else {
assertionFailure("输入字符串格式不对,每个字符都需要在0~9,a~f,A~F内")
}
sum = sum * 16 + intC
// 每两个十六进制字母代表8位,即一个字节
if index % 2 != 0 {
bytes.append(UInt8(sum))
sum = 0
}
}
let data = Data(bytes: bytes)
return data } //16进制Data 转 String
class func string(from data:Data)->String { return data.map { String(format: "%02x", $0) }
.joined(separator: "") } // MARK: - 十进制转二进制
class func decTobin(number:Int) -> String {
var num = number
var str = ""
while num > 0 {
str = "\(num % 2)" + str
num /= 2
}
return str
} // MARK: - 二进制转十进制
class func binTodec(number num: String) -> Int {
var sum: Int = 0
for c in num {
let str = String(c)
sum = sum * 2 + Int(str)!
} return sum
} // MARK: - 十进制转十六进制
class func decTohex(number:Int) -> String {
return String(format: "%0X", number)
} // MARK: - 十六进制转十进制
class func hexTodec(number num:String) -> Int {
let str = num.uppercased()
var sum = 0
for i in str.utf8 {
sum = sum * 16 + Int(i) - 48 // 0-9 从48开始
if i >= 65 { // A-Z 从65开始,但有初始值10,所以应该是减去55
sum -= 7
}
}
return sum
}

iOS 蓝牙开发详解的更多相关文章

  1. iOS应用开发详解

    <iOS应用开发详解> 基本信息 作者: 郭宏志    出版社:电子工业出版社 ISBN:9787121207075 上架时间:2013-6-28 出版日期:2013 年7月 开本:16开 ...

  2. iOS自定义控件开发详解

    http://blog.csdn.net/zhangao0086/article/details/45622875

  3. iOS原生地图开发详解

    在上一篇博客中:http://my.oschina.net/u/2340880/blog/414760.对iOS中的定位服务进行了详细的介绍与参数说明,在开发中,地位服务往往与地图框架结合使用,这篇博 ...

  4. [转载]Apple Watch 开发详解

    Apple Watch 开发详解 Apple Watch现在对于第三方开发者来说更多的还是一块额外的屏幕.暂时WatchKit没有能给出足够的接口.现在Watch App的主要运算逻辑需要依赖iPho ...

  5. Apple Watch 开发详解

    Apple Watch 开发详解 Apple Watch现在对于第三方开发者来说更多的还是一块额外的屏幕.暂时WatchKit没有能给出足够的接口.现在Watch App的主要运算逻辑需要依赖iPho ...

  6. 转载]IOS LBS功能详解[0](获取经纬度)[1](获取当前地理位置文本 )

    原文地址:IOS LBS功能详解[0](获取经纬度)[1](获取当前地理位置文本作者:佐佐木小次郎 因为最近项目上要用有关LBS的功能.于是我便做一下预研. 一般说来LBS功能一般分为两块:一块是地理 ...

  7. javacv开发详解之1:调用本机摄像头视频(建议使用javaCV1.3版本)

    javaCV系列文章: javacv开发详解之1:调用本机摄像头视频 javaCV开发详解之2:推流器实现,推本地摄像头视频到流媒体服务器以及摄像头录制视频功能实现(基于javaCV-FFMPEG.j ...

  8. 了解iOS消息推送一文就够:史上最全iOS Push技术详解

    本文作者:陈裕发, 腾讯系统测试工程师,由腾讯WeTest整理发表. 1.引言 开发iOS系统中的Push推送,通常有以下3种情况: 1)在线Push:比如QQ.微信等IM界面处于前台时,聊天消息和指 ...

  9. Xamarin+Prism开发详解七:Plugin开发与打包测试

    有了上章[Xamarin+Prism开发详解六:DependencyService与IPlatformInitializer的关系]的基础,现在来理解Plugin开发就简单了. 本文实例代码地址:ht ...

随机推荐

  1. Golang的运算符-比较运算符

    Golang的运算符-比较运算符 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.比较运算符概述 比较运算符也称为关系运算符,比较运算符返回的类型为bool类型,常见的比较运算符 ...

  2. 数据结构——KMP(串)

    KMP一个非常经典的字符串模式匹配算法,虽然网上有很多kmp算法的博客,但是为了更好的理解kmp我还是自己写了一遍(这个kmp的字符串存储是基于堆的(heap),和老师说的定长存储略有不同,字符串索引 ...

  3. 2.9 学习总结 之 【Android】体温统计APP

    一.说在前面 昨天 学习了JQ的相关知识 今天 编写体温统计APP 我的工程源码:https://github.com/xiaotian12-call/Take-body-temperature 二. ...

  4. HihoCoder第七周:完全背包问题

    1043 : 完全背包 时间限制:20000ms 单点时限:1000ms 内存限制:256MB 描述 且说之前的故事里,小Hi和小Ho费劲心思终于拿到了茫茫多的奖券!而现在,终于到了小Ho领取奖励的时 ...

  5. pr cs6安装教程

    这是通过我自己实践操作,网上查询整理的安装流程: 安装 1.下载:http://www.smzy.com/smzy/smzy93225.html 2.断网,安装 如果到2%显示安装失败,在这里有详细解 ...

  6. java调用存储过程(stored procedures)的HelloWorld例子

    1.java调用存储过程(stored procedures)的HelloWorld程序 有点数据 库基础的人都知道.存储过程(stored procedures)和java没什么关系.它是一段纯粹的 ...

  7. Sklearn K均值聚类

    ## 版权所有,转帖注明出处 章节 SciKit-Learn 加载数据集 SciKit-Learn 数据集基本信息 SciKit-Learn 使用matplotlib可视化数据 SciKit-Lear ...

  8. kibana下载与安装

    目录 简介 下载 安装 测试 简介 Kibana是一个为ElasticSearch 提供的数据分析的 Web 接口.可使用它对日志进行高效的搜索.可视化.分析等各种操作.安装之前有话说: 安装路径不要 ...

  9. OpenCV3 Ref SVM : cv::ml::SVM Class Reference

    OpenCV3  Ref SVM : cv::ml::SVM Class Reference OpenCV2: #include <opencv2/core/core.hpp>#inclu ...

  10. Kubernetes-基于helm安装部署高可用的Redis及其形态探索(二)

    上一章,我们通过实践和其他文章的帮助,在k8s的环境安装了redis-ha,并且对其进行了一些实验来验证他的主从切换是否有效.本篇中将会分析,究竟是如何实现了redis-ha的主从切换,以及其与K8S ...