今天说一说iOS蓝牙相关的东西,本文背景是公司的蓝牙项目,项目要求是利用手机蓝牙模块与低功耗蓝牙卡进行通信,蓝牙卡信息解析由卡厂商提供,而我们先要做的就是建立手机与蓝牙卡的连接。难点主要集中在与蓝牙卡连接断开部分,因为蓝牙卡是低能耗的,每开启蓝牙卡片蓝牙后它会在8秒后自动断开连接,所以在处理蓝牙连接的的部分逻辑较为复杂。接下来我们把重点放在与蓝牙建立连接的部分,Google之,先来搞清楚与iOS有关的蓝牙库。

先说一下蓝牙版本问题,如果你的设备支持的是蓝牙4.0之前的版本,那么会涉及到一个MFI的概念,MFI(Make For ipod/ipad/iphone)是苹果的一套认证,只有少数的硬件厂商才有苹果的MFI认证,做之前需要搞定这个认证。使用蓝牙4.0的话,由于4.0苹果开放了BLE(Bluetooth Low Energy)通道,就不会有认证的问题了,而且向下兼容。
我们用到的蓝牙库为CoreBluetooth,而蓝牙库中首先要介绍下两个概念Central和Peripheral;

Central 和 Peripheral 在蓝牙交互中的角色

所有涉及蓝牙低功耗的交互中有两个主要的角色:中心Central和外围设备Perpheral。根据一些传统的客户端-服务端结构,Peripheral通常具有其他设备所需要的数据,而Central通常通过使用Perpheral的信息来实现一些特定的功能。

这里我自己理解,如果你的设备连接的是本文这种蓝牙卡或者穿戴设备等,那么你的程序就是作为Central;如果你的设备是与另外一台iPhone设备,那么它既可以作为Central也可以作为Perpheral;

想了解更详细请参照:iOS蓝牙编程指南 – 核心蓝牙概述

UUID

每个蓝牙4.0的设备都是通过服务和特征来展示自己的,一个设备必然包含一个或多个服务,每个服务下面又包含若干个特征。特征是与外界交互的最小单位。比如说,一台蓝牙4.0设备,用特征A来描述自己的出厂信息,用特征B来与收发数据等。

服务和特征都是用UUID来唯一标识的,UUID的概念如果不清楚请自行google,国际蓝牙组织为一些很典型的设备(比如测量心跳和血压的设备)规定了标准的service UUID(特征的UUID比较多,这里就不列举了);

UUID含义是通用唯一识别码 (Universally Unique Identifier),这是一个软件建构的标准,也是被开源软件基金会 (Open Software Foundation, OSF) 的组织应用在分布式计算环境 (Distributed Computing Environment, DCE) 领域的一部分。
UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。

UUID由以下几部分的组合:

(1)当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。

(2)时钟序列。

(3)全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
UUID的唯一缺陷在于生成的结果串会比较长。关于UUID这个标准使用最普遍的是微软的GUID(Globals Unique Identifiers)。在ColdFusion中可以用CreateUUID()函数很简单地生成UUID,其格式为:xxxxxxxx-xxxx- xxxx-xxxxxxxxxxxxxxxx(8-4-4-16),其中每个 x 是 0-9 或 a-f 范围内的一个十六进制的数字。而标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12),可以从cflib 下载CreateGUID() UDF进行转换。

BLE中心模式流程

1.建立中心角色

2.扫描外设(Discover Peripheral)

3.连接外设(Connect Peripheral)

4.扫描外设中的服务和特征(Discover Services And Characteristics)

5.利用特征与外设做数据交互(Explore And Interact)

6.订阅Characteristic的通知

7.断开连接(Disconnect)

代码说明

初始化 CBCentralManager

dispatch_queue_t centralQ = dispatch_queue_create(BLUETOOCH_QUEUE_IDENTIFER, DISPATCH_QUEUE_CONCURRENT);_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQ];

上面的代码中,将self设置为代理,用于接收各种 central 事件。将queue设置为nil,则表示直接在主线程中运行,这里是我自己定义的任务队列。

创建Central管理器时,管理器对象会调用代理对象的centralManagerDidUpdateState:方法。我们需要实现这个方法来确保本地设备支持BLE。

初始化 central manager 之后,设置的代理会调用centralManagerDidUpdateState:方法,所以需要去遵循协议。这个 did update state 的方法,能获得当前设备是否能作为 central。关于这个协议的实现和其他方法,接下来会讲到,也可以先看看官方API

搜索当前可用的 peripheral

可以使用CBCentralManager的scanForPeripheralsWithServices:options:方法来扫描周围正在发出广播的 Peripheral 设备。peripheral 每秒都在发送大量的数据包,scanForPeripheralsWithServices:options:方法会将同一 peripheral 发出的多个数据包合并为一个事件,然后每找到一个 peripheral 都会调用 centralManager:didDiscoverPeripheral:advertisementData:RSSI: 方法。另外,当已发现的 peripheral 发送的数据包有变化时,这个代理方法同样会调用。

NSArray *services = @[[CBUUID UUIDWithString:BUSINESS_SERVICE_UUID_STRING]
NSDictionary *scanOption = @{CBCentralManagerScanOptionAllowDuplicatesKey:@(NO)};
[_centralManager scanForPeripheralsWithServices:services options:scanOption];

这里的services是中心要扫描的蓝牙设备类型,表示只搜索当前数组包含的设备(每个 peripheral 的 service 都有唯一标识——UUID);而scanOption中的CBCentralManagerScanOptionAllowDuplicatesKey设置以后,每收到广播,就会调用上面的回调(无论广播数据是否一样)。关闭默认行为一般用于以下场景:根据 peripheral 的距离来初始化连接(距离可用信号强度 RSSI 来判断)。设置这个 option 会对电池寿命和 app 的性能产生不利影响,所以一定要在必要的时候,再对其进行设置。

在调用scanForPeripheralsWithServices:options:方法之后,找到可用设备,系统会回调(每找到一个都会回调)centralManager:didDiscoverPeripheral:advertisementData:RSSI:。该方法会已CBPeripheral返回找到的 peripheral,所以你可以使用数组将找到的 peripheral 存起来。

//扫描到蓝牙后的回调
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{//RSSI(Received Signal Strength Indication接收的信号强度指示)   printf("didDiscoverPeripheral\n");NSLog(@"advertisement data is :%@",advertisementData);NSString* identifer = [peripheral.identifier UUIDString];
}

连接 peripheral

//连接外围设备
[_centralManager connectPeripheral:peripheral options:nil];

当连接成功后,会回调方法centralManager:didConnectPeripheral:。在这个方法中,你可以去记录当前的连接状态等数据。

-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{printf("已连接上设备:");printf("name = %s\n",[peripheral.name UTF8String]);//连接到设备后要设置设备的代理,这样才可以接收到外围的服务与特性peripheral.delegate = self;NSArray<CBUUID*>* uuids =@[[CBUUID UUIDWithString:WRITE_CHARACTERISTIC_UUID_STRING],[CBUUID UUIDWithString:READ_CHARACTERISTIC_UUID_STRING]];//发现服务[peripheral discoverServices:uuids];
}

如果连接断开则会回调:

//断开回调处理
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{printf("设备 %s 已断开!\n",[peripheral.name UTF8String]);
}

失败的情况下则是:

//连接失败回调
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{NSLog(@"didFailToConnectPeripheral error:%@",error);
}

搜索 peripheral 的 service

当与 peripheral 成功建立连接以后,就可以通信了。第一步是先找到当前 peripheral 提供的 service,因为 service 广播的数据有大小限制(貌似是 31 bytes),所以你实际找到的 service 的数量可能要比它广播时候说的数量要多。调用CBPeripheral的 discoverServices:方法可以找到当前 peripheral 的所有 service。

//在搜索过程中,并不是所有的 service和characteristic 都是我们需要的,如果全部搜索,依然会造成不必要的资源浪费。
//这里的uuids是我工程中用到的UUID
NSArray<CBUUID*>* uuids =@[
[CBUUID UUIDWithString:WRITE_CHARACTERISTIC_UUID_STRING],
[CBUUID UUIDWithString:READ_CHARACTERISTIC_UUID_STRING]];
//发现服务
[peripheral discoverServices:uuids];

当找到特定的 Service 以后,会回调的peripheral:didDiscoverServices:方法。Core Bluetooth 提供了CBService类来表示 service,找到以后,它们以数组的形式存入了当前 peripheral 的services属性中,你可以在当前回调中遍历这个属性。

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ if(error){NSLog(@"发现服务错误:%@",error);return;}printf("发现周边设备的服务:\n");printf("==== didDiscoverServices ==== \n");//发现服务中的特性 for (CBService *service in peripheral.services) {printf("-- service : %s\n",[[service.UUID UUIDString] UTF8String]);[peripheral discoverCharacteristics:nil forService:service];}
}

搜索 service 的 characteristic

找到需要的 service 之后,下一步是找它所提供的 characteristic。如果搜索全部 characteristic,那调用CBPeripheral的discoverCharacteristics:forService:方法即可。如果是搜索当前service的characteristic,那还应该传入相应的CBService对象:

[peripheral discoverCharacteristics:nil forService:service];

找到所有 characteristic 之后,回调peripheral:didDiscoverCharacteristicsForService:error:方法,此时 Core Bluetooth 提供了CBCharacteristic类来表示characteristic。可以通过以下代码来遍历找到的 characteristic :

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{printf("发现服务 :(%s)\n",[[service.UUID UUIDString] UTF8String]);if (error) {NSLog(@"There is a error in peripheral:didDiscoverCharacteristicsForService:error: which called:%@",error);return;} printf("开始读取服务数据...\n");for (CBCharacteristic *characteristic in service.characteristics) {NSLog(@"properties is %lu",characteristic.properties);if (characteristic.properties & CBCharacteristicPropertyNotify) {
//            [peripheral readValueForCharacteristic:characteristic];[peripheral setNotifyValue:YES forCharacteristic:characteristic];}}
}

读取 characteristic 数据

这里读取涉及到两个方法:

[peripheral readValueForCharacteristic:characteristic];
[peripheral setNotifyValue:YES forCharacteristic:characteristic];

read这种方法是需要主动去接收的;notify方法订阅,当有数据发送时,可以直接在回调中接收,如果 characteristic 的数据经常变化,那么采用订阅的方式更好;

//获取外设发来的数据,不论是read和notify,获取数据都是从这个方法中读取。
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{if (error) {NSLog(@"There is a error in peripheral:didUpdateValueForCharacteristic:error: which called:%@",error);return;}NSLog(@"characteristic data is:%@ ",characteristic.value);NSLog(@"characteristic data length is %ld",characteristic.value.length);
}

所以nofify可能会被调用多次,而且它获取的是实时数据,如果你接收蓝牙信息不是一次次接收的话,那么会用到它:

//中心读取外设实时数据
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{printf("didUpdateNotificationStateForCharacteristic (%s)\n",[[characteristic.UUID UUIDString] UTF8String]);if(error){printf("error is : %s\n",[error.description UTF8String]);return;}printf(" update notification success !!");NSLog(@"接收到的数据:%@",characteristic.value);}
}

本文参考,之后会继续讲解写数据和重连等问题;

iOS 作为Central蓝牙连接外围(上)相关推荐

  1. android7.0蓝牙连不上,苹果7蓝牙连接不上是什么原因_苹果蓝牙连接不上的处理方法-系统城...

    在使用苹果7手机时我们会经常用到蓝牙设备:例如蓝牙音响,蓝牙耳机等.然而最近有网友在操作时却遇到了蓝牙连接不上的情况,很影响自己的使用,我们该怎么办呢?今天小编就来教大家关于苹果蓝牙连接不上的处理方法 ...

  2. php蓝牙连接不上,Mac蓝牙不可用怎么办?苹果电脑Mac蓝牙连不上i

    近日,很多用户在升级Yosemite一段时间后,发现蓝牙不可用了,那么,Mac蓝牙不可用是怎么回事?如何解决?针对此问题,本文就为大家介绍Mac蓝牙连不上iphone现象的解决办法 Mac蓝牙不可用怎 ...

  3. 黑苹果关机重启后蓝牙连接不上_手机要关机吗,要贴膜吗,要套壳吗?看完这条,都有答案了_政务_澎湃新闻...

    晚上习惯关机睡觉的朋友们在哪里? 举起你们的手让小布看看! 那么问题来了,你为啥要关机睡觉? 是担心手机"睡眠不足", 还是担心屏亮会让你忍不住地熬夜? 然而,有朋友认为手机应该每 ...

  4. Mac安装win10双系统蓝牙连接不上故障问题

    MAC安装双系统之后,进入win10一直不能正常连接蓝牙设备,鼠标也好,键盘也罢, 通通连接不上 结果多次试验网上的方法,通通不能用,依然不能连接. 分析问什么: 1.进入win10是从mac重启启动 ...

  5. android mac蓝牙连接不上,Mac蓝牙找不到设备解决方法介绍

    很多新手用户不知道怎么在mac系统中连接蓝牙设备,其实方法还是很简单的,当然如果你想要进一步进行了解的话倒是可以跟随小编一起加入这款Mac蓝牙找不到设备解决方法介绍的文章中仔细了解衣服哦,希望小编为你 ...

  6. 【日志】罗技鼠标m590 usb或蓝牙连接不上 无法使用

    m590模式为usb连接,插上优联连接器,灯1快速闪烁,无法连接. 解决方法: (1)首先查看蓝牙设备,看是否识别为罗技优联连接器.否则下载Logitech官网固件更新工具. (2)若是仍不可连接使用 ...

  7. 苹果蓝牙连接不上是什么原因_无线网连接不上 原因很多,总有一个办法解决你的问题...

    无线网卡可以搜到无线信号,但是无法连接,可以参考以下步骤排查. 解决方法 1.确认是否连接的自己的无线信号 电脑使用有线连接路由器,并登录路由器管理界面,在 路由设置 >> 无线设置 中查 ...

  8. linux无线网卡蓝牙连接不上,Linux如何解决英特尔无线网卡WiFi网速慢、WiFi蓝牙无法共存等问题...

    使用本教程前,请先确认你机器里有英特尔无线网卡.该教程只适用于英特尔无线网卡.其他无线网卡修改iwlwifi.conf没有任何效果! 无线网卡的型号可以在"设备管理器"应用中查看, ...

  9. win7蓝牙怎么连接_自拍杆蓝牙连接不上手机怎么办?求人不如求己,3~5分钟完事。...

    昨天大学生开学了,车多人多,学生开学本来带东西也多,开车也是情理之中.送完学生,去附近的景区转一转吧,去了以后自拍杆带上了,一个人可以根据自己的需要来调整位置角度方便自如. 就在把手机安装在自拍杆上以 ...

最新文章

  1. 深入理解Java注解Annotation及自定义注解
  2. heidiSQL使用简介
  3. java继承机制实验报告_JAVA继承实验报告.doc
  4. 日常生活小技巧 -- win10造字
  5. php 缩略图 失真,WORDPRESS缩略图失真变形模糊的解决方法
  6. 程序导致IIS服务器应用程序池停止
  7. 智慧城市开放数据的发展趋势分析及探讨
  8. python21天打卡day4
  9. python自学网站-自学Python网站推荐 从入门到精通
  10. 国人不能再过度迷信开源软件
  11. Ubuntu 文件文件夹查看权限和设置权限
  12. atitit.技术选型方法总结为什么java就是比.net有前途
  13. 波士顿房价预测实验报告
  14. CS5216|DP转HDMI方案详解|分辨率1080P
  15. GIS空间分析之公路选线
  16. 十八、DPM模型案例(二)
  17. Tensorflow的基本使用方法
  18. 微信小程序实现页面加入背景图片以及调节透明度
  19. Ubuntu vi 方向键乱码 问题解决
  20. 接近8000字的Spring/SpringBoot常用注解总结!安排!

热门文章

  1. win10 wireshark不显示网卡问题
  2. Unity3D 鼠标拖动摄像机左右移动
  3. [用户分享]CloudCanal助力万店掌MySQL同步ES构建宽表场景
  4. 显示人社服务器异常,居住证办理时提示人社在线接口异常无法获取社保信息该怎么办...
  5. 对MarshalByRefObject的讲解
  6. 新人如何入行游戏美术设计师
  7. Ubuntu 升级pip3后,pip3和pip同指向问题解决
  8. 鸡兔同笼问题 Java解法——(暴力破解)
  9. 研控步进电机Canopen通讯测试
  10. 基于SSM的社会救助信息管理 毕业设计-附源码211633