蓝牙(CoreBluetooth)-外部设备(服务端)

主要内容

1. 创建外部管理器对象
2. 设置本地外设的服务和特征
3. 添加服务和特征到到你的设置的数据库中
4. 向外公布你的的服务
5. 相应来自连接上的中心设备的请求
6. 向订阅了特征值改变的中心设备发送通知

1. 创建外设管理器

首先你需要创建一个CBPeripheralManager 对象,通过CBPeripheralManager的initWithDelegate:queue:options:,像这样:

    self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];

这里设置self来作为外设的任何事件的接受者.

queue:表示外设管理器分发事件的队列,当你指定为nil,外设管理器将在主队列中的分发外设事件.

当你创建CBPeripheralManager对象时,这个管理器对象会调用peripheralManagerDidUpdateState:方法,你必须实现这个来判断你设备是否支持并开启蓝牙4.0

2. 设置服务和特征

你必须按树形结构进行管理服务和特征,树结构如图

外设,服务,特征结构图

1. 服务和特征通过UUID来标示的

外设的服务和和特征通过一个128位的蓝牙UUID来标示的,在在CoreBlueTooth框架中是通过CBUUID来表示的,尽管不是所有服务和特质的UUID都需要通过蓝牙特定兴趣组来(SIG[ Special Interest Group])重定义,但是通过SIG定义或分发的通用UUID可以被缩短为16位.例如:通过蓝牙SIG定义表示心率的服务的16为UUID180D,这个UUID是蓝牙4.0,特定的服务UUID0000180D-0000-1000-8000-00805F9B34FB简便形式.
CBUUID *heartRateServiceUUID = [CBUUID UUIDWithString: @"180D"];
当你通过16为的简便形式创建一个UUID,核心蓝牙会把填满为128位的UUID通过基UUID

2. 创建你自己的服务和特征的UUID,

1. 你可以通过终端生成一个UUID
$ uuidgen
71DA3FD1-7E10-41C1-B16F-4430B506CDE7
2. 你可以使用这个UUID,通过CBUUID的UUIDWithString:方法创建一个CBUUID对象,像这样
CBUUID *myCustomServiceUUID =
[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];

3. 构建你的服务和特征树

当你有了服务或特征标示你就可以按照上面的特征服务树来组织你的服务和特征.你可以通过CBMutableCharacteristic的initWithType:properties:value:permissions:来创建一个可变特征,像这样

    ```
myCharacteristic =
[[CBMutableCharacteristic alloc] initWithType:myCharacteristicUUID
properties:CBCharacteristicPropertyRead
value:myValue permissions:CBAttributePermissionsReadable];```

当你创建一个可变的特征的时候,你就可以指定他的属性,值和许可.你可以通过这些属性和权限来指定这个特征的值是可读的或可写的和连接上的中心设备能否可以订阅这个值,在这个例子中,中心设备可以读取特征的值,更多信息参考CBMutableCharacteristic 类

注意: 当你给某个特征指定一个值,这个将被缓存 并且它的属性和许可被设置为可读.因此,如果你需要这个该特征的值是可写的或如果希望这个特征的值在其所属服务的生命周期中是可以改变的,你必须指定这个值为nil.这样做来确保这个值是动态的和当外设管理器接收来自中心设备的读和写请求是,可以被外设管理器请求.

现在你有一个可变特征后,你可以创建一个服务,然后关联上该特征.你可以通过CBMutableService的initWithType:primary:方法,如下:

    myService = [[CBMutableService alloc] initWithType:myServiceUUID primary:YES];

在这个例子中,第二个参数设置为YES ,它说明这个服务是一个主要的服务而不是次要的,主服务描述了一个服务的主要功能,他可以包含其他的服务,次要服务必须关联在他所参照的其他服务的上下文中.比如:心率检测器的主要服务是提供心率的数据的,那么次要服务可能就是提供心率检测器的电量数据的.

在你创建服务之后,你可以关联那个特征到这个服务上,像这样:

  myService.characteristics = @[myCharacteristic];

3. 添加布服务和特征到外设数据库

在你创建了服务和特征之后,下一步就是添加服务到设备的服务和特征数据库中,在蓝牙核心框架中很容易做.你只需要调用CBPeripheralManager 的 addService: 方法即可,像这样:

 [myPeripheralManager addService:myService];

当你通过这个方法添加服务到服务特征数据库中时,外设管理器会调用代理的peripheralManager:didAddService:error:方法,当分发失败了会调用这个方法,实现这个方法可以查看错误原因

- (void)peripheralManager:(CBPeripheralManager *)peripheral
didAddService:(CBService *)service
error:(NSError *)error { if (error) {
NSLog(@"Error publishing service: %@", [error localizedDescription]);
}
...

注意:当你发布一个关联在外设数据库的服务和特征时,这个服务将被缓存,并且你以后再也不能在修改它了.

4. 公布你的服务给监听的中心管理器

当你已经添加服务和特征到外设的服务特征数据库中后,你已经公布给服务或特征给中心管理器做好准备,你可以通过你的CBPeripheralManager的startAdvertising:,传入一个包含需要公布数据的字典.

 [myPeripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey :
@[myFirstService.UUID, mySecondService.UUID] }];

这个例子中通过CBAdvertisementDataServiceUUIDsKey 这个key来指定的需要公布分服务的 UUID,可能允许使用的key,在CBCentralManagerDelegate Protocol Reference.的Advertisement Data Retrieval Keys 中描述:

1. CBAdvertisementDataLocalNameKey 对应的值是一个字符串,描述外设的名称
2.CBAdvertisementDataManufacturerDataKey 对应的值是一个NSData对象,包含外设的产生的数据
3. CBAdvertisementDataServiceDataKey 包含特定服务的分发数据,该字典的key为代表着该服务的CBUUID对象.值为NSData对象
4. CBAdvertisementDataServiceUUIDsKey 需要公布的服务的`UUID`数组
5. CBAdvertisementDataOverflowServiceUUIDsKey 代表着在公布数据的"overflow"区域能够被发现的服务的UUID的数组,因为存储在这个`UUID`列表是`最大努力的` 并且不总是精确的.如果设备资源不足这些属性可能不会被公布.
6. CBAdvertisementDataTxPowerLevelKey 一个包含外设发射功率NSNumber的数字,如果外设在广播的数据包中,提供了他的`Tx`功率级别时候,这个属性是可用的. 使用这个`RSSI` 值和电台功率,计算出路径损耗是有可能.
7. CBAdvertisementDataIsConnectable 一个布尔值,标示公布事件类型是否为可连接的,对应这个Key是一个 NSNumber对象,你可用使用这个值来检查一个外设当前是否为连接状态
8. CBAdvertisementDataSolicitedServiceUUIDsKey 一个代表着一个或多个服务的`UUID`

但是对于外设管理器只支持两个key CBAdvertisementDataLocalNameKey和 CBAdvertisementDataServiceUUIDsKey.

当你在你的本地外设开始公布公布你的外设数据的时候,会调用外设管理器代理的peripheralManagerDidStartAdvertising:error:,你可以通过实现这个方法,来查看你公布数据时的错误

- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(NSError *)error { if (error) {
NSLog(@"Error advertising: %@", [error localizedDescription]);
}
...

注意: 数据发布是基于最大努力的,由于带宽是有限并且多个app可能同时发布,更多信息参考CBPeripheralManager的startAdvertising: 的注释.
'当你应用后台也可以进行发布行为,这个话题参照core bluetooth文档的Core Bluetooth Background Processing for iOS Apps

一旦你开始公布数据,远程的中心设备就可用发现并连接上你.

5. 响应来自中心设备的读和写请求

当你连接上一个或多个中心设备后,你便开始接受来自他们的读或写请求,请你确保以合适方式来响应的这些请求,下面的例子来描述如何相应这些请求

当一个中心管理器对去某个特征中的值的时候,会调用外设管理器的代理方法peripheralManager:didReceiveReadRequest:,这个方法传入了一个CBATTRequest的请求,他提供很多属来满足这个请求

例如当你接受到一个单独读取某个特征值的请求,代理方法传入的CBATTRequest对象的属性课用来判断你设备数据中的特征是否与远程中心设备请求的特征是否匹配,你可以向这样的来实现这个方法

    - (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request { if ([request.characteristic.UUID isEqual:myCharacteristic.UUID]) {
...

如果特征是匹配的,那么下一步是确保请求读取数据的索引没有超出特征值的范围.下面的例子向你展示如何使用CBATTRequest对象的offset 来确保取得的索引没有超出特征值的边界

  if (request.offset > myCharacteristic.value.length) {
[myPeripheralManager respondToRequest:request
withResult:CBATTErrorInvalidOffset];
return;
}

假定已经验证通过,你可以通过本地外设的特征的值给请求设置值(它默认是nil)

    request.value = [myCharacteristic.value
subdataWithRange:NSMakeRange(request.offset,
myCharacteristic.value.length - request.offset)];

在你设置值之后,你需要告知远程的中心设置已经成功设置,通过明确调用CBPeripheralManager的respondToRequest:withResult:方法,像这样:

[myPeripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
...

每次在代理对象的peripheralManager:didReceiveReadRequest:方法中明确调用respondToRequest:withResult:

注意: 如果特征的UUID不匹配或因任何原因导致的读不能完成.你不要试图来填充请求的值,而是立即调用respondToRequest:withResult:方法提供一个失败的原因.详情参照CBATTError枚举

处理来自中央设备的写请求是简单的,当一个中心设备发送对象一个或多个特征值的写请求时,外设管理器就会调用其代理peripheralManager:didReceiveWriteRequests:方法,在这个方法中传入了一个CBATTRequest对象的数组.每一个都代表着一个写请求.在确认请示是可以被满足的后,你可以写特征的值,像这样:

    myCharacteristic.value = request.value;

尽管上面的例子中没有演示,当写入你的特征值时,如何确保写入的偏移量.但是在你真实的app中需要进行处理.

和是响应读请求一样,在代理方法peripheralManager:didReceiveWriteRequests:明确调用一次respondToRequest:withResult: 方法.这也就是说第一个参数是单独一个请求对象,尽管你可能在peripheralManager:didReceiveWriteRequests:方法中受到了多个请求对象.你应该传入数组中的第一个请求作为参数.像这样:

   [myPeripheralManager respondToRequest:[requests objectAtIndex:0]
withResult:CBATTErrorSuccess];

注意:对于多个请求对象只有一次请求,只要任意的一个请求对象得不到满足,你任意一个都不要满足,而是立即调用respondToRequest:withResult:方法提供一个结果,告诉失败的原因.

6. 发送更新特征的值给已经订阅的中心设备

通常情况,连接上的中央设备将订阅你的一个或多个特征值, 当他们这么做的时候,你有责任当某个订阅特征的值发生改变了通知他们.下面的例子描述如何做.

当一个连接上的中心管理器订阅了你的特征上的某个值,那么外设管理器就会调用其代理的peripheralManager:central:didSubscribeToCharacteristic:方法调用

    - (void)peripheralManager:(CBPeripheralManager *)peripheral
central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic { NSLog(@"Central subscribed to characteristic %@", characteristic);
...

使用上面的方法作为一个开始发送更新中心设备数据的引子,接下获取特征的更新值并调用CBPeripheralManager 的 updateValue:forCharacteristic:onSubscribedCentrals:把更新数据发送给中心设备

   NSData *updatedValue = // fetch the characteristic's new value
BOOL didSendValue = [myPeripheralManager updateValue:updatedValue
forCharacteristic:characteristic onSubscribedCentrals:nil];

当你通过上面的方法给订阅的中心设备发送更新数据时,你可以通过最后一个参数指定更新那些中心设备,如果传入nil,表示所有订阅的中心设备都更新数据(连接上没有订阅的中心设备讲被忽略).该方法返回一个Bool,来指示是否发送成功,如果用于传入更新值的队列是满的,那么该方法返回NO,当传输队列可用的时,会调用外设管理器的代理peripheralManagerIsReadyToUpdateSubscribers:,你可以实现这个方法再次传送数据

注意:使用这个方法发送通知给订阅的中心设备管理器一个单独的数据包,当你更新数据的应该把整个数据作为一个通知发送.只调用一次updateValue:forCharacteristic:onSubscribedCentrals:方法,如果你的数据不能通过一次通知传递过去,解决方案就是在中心设备一边,通过 CBPeripheral的 readValueForCharacteristic:方法,来获取数据,这个方法可以取回整个数据.

外设流程流程图

蓝牙(CoreBluetooth)-外部设备(服务端)的更多相关文章

  1. 低功耗蓝牙BLE外围模式(peripheral)-使用BLE作为服务端

    低功耗蓝牙BLE外围模式(peripheral)-使用BLE作为服务端 Android对外模模式(peripheral)的支持 从Android5.0开始才支持 关键术语和概念 以下是关键BLE术语和 ...

  2. Android-低功耗蓝牙(BLE)-客户端(主机/中心设备)和服务端(从机/外围设备)

    一.Android 低功耗蓝牙(BLE)的API简介 从Android 4.3(API 18)才支持低功耗蓝牙(Bluetooth Low Energy, BLE)的核心功能, BLE蓝牙协议是GAT ...

  3. 蓝牙(CoreBluetooth)-概述

    蓝牙(CoreBluetooth)-概述 通过此框架可以让你的Mac和iOS应用程序与外部蓝牙设备通信 外部设备: 就是需要通过iOS App控制器的其他设备: 例如:心率检测仪.数字温控器 蓝牙通讯 ...

  4. 蓝牙 CoreBluetooth

    baseK(相关基础知识)蓝牙常见名称和缩写 BLE:(Bluetooth low energy)蓝牙4.0设备因为低耗电,也叫BLEperipheral,central:外设和中心设备,发起链接的是 ...

  5. 蓝牙(CoreBluetooth)-中心设备(客户端)

    蓝牙(CoreBluetooth)-中心设备(客户端) 蓝牙客户端-中心设备 主要内容 1. 创建`中央管理器` 2. 发现并且连接外设 3. 寻找连接上的外设数据 4. 发送读或写`特征值`的请求 ...

  6. Zabbix配置文件详解之服务端zabbix_server

    zabbix作为运维邻域不可缺少的一员,它的各种文档可是数不胜数啊,但是关于配置文件的解释与说明就有点少.这里列出zabbix配置文件篇之zabbix_server. Zabbix Server端配置 ...

  7. Android BLE与终端通信(三)——客户端与服务端通信过程以及实现数据通信

    Android BLE与终端通信(三)--客户端与服务端通信过程以及实现数据通信 前面的终究只是小知识点,上不了台面,也只能算是起到一个科普的作用,而同步到实际的开发上去,今天就来延续前两篇实现蓝牙主 ...

  8. 网络编程、三要素、Socket通信、UDP传输、TCP协议、服务端(二十五)

    1.网络编程概述 * A:计算机网络 * 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传 ...

  9. Android BLE与终端通信(三)——client与服务端通信过程以及实现数据通信

    Android BLE与终端通信(三)--client与服务端通信过程以及实现数据通信 前面的终究仅仅是小知识点.上不了台面,也仅仅能算是起到一个科普的作用.而同步到实际的开发上去,今天就来延续前两篇 ...

随机推荐

  1. 第七章Openwrt安装服务器环境php+uhttpd+mysql

    在前面的文章中刷openwrt.配置网络环境.挂载u盘都配置成功了之后,下面的操作就变得简单起来!!!! 1. putty连接到路由器 2. 安装php opkg install php5-fastc ...

  2. IOS是否在项目中存在,所使用的反射那点事

    NSClassFromString,NSSelectorFromString,isKingOfClass 1. NSClassFromString 这种方法推断类是否存在,假设存在就动态载入的,不存为 ...

  3. GPU bubbles

    https://software.intel.com/en-us/articles/performance-analysis-and-optimization-for-pc-based-vr-appl ...

  4. echarts图形报表缓存问题(option数据缓存)

    这几天我在工作中用到了echarts开发报表.每次查询出来的数据都是新的,但是echart展现的图形报表却还是之前的数据.网上找了搜索了很多次也没能解决,后面加了技术群才解决的. 我开始已经确定是报表 ...

  5. Spark Streaming updateStateByKey和mapWithState源码解密

    本篇从二个方面进行源码分析: 一.updateStateByKey解密 二.mapWithState解密 通过对Spark研究角度来研究jvm.分布式.图计算.架构设计.软件工程思想,可以学到很多东西 ...

  6. 转:CodeReview 如何做?

    http://www.cnblogs.com/IT-Bear/archive/2012/07/04/2576367.html

  7. 转:关于Android机型适配这件小事儿

    http://www.umindex.com/devices/android_resolutions 大家都知道Android机型分裂严重,在开发Android App的时候永远都面临适配N多机型的问 ...

  8. 使用HTML5画布(canvas)生成阴影效果

    来源:GBin1.com 使用HTML5的画布特性,我们可以创建图形,在这片文章中,我们将创建图形的阴影. var canvas = document.getElementById('shadowca ...

  9. Android Studio之基本设置与运行

    项目结构 当我们新建一个项目的目录结构默认是这样的 可以看到和Eclipse的目录结构有很大区别,Studio一个窗口只能有一个项目,而Eclipse则可以同时存在很多项目,如果你看着不习惯可以点击左 ...

  10. ant 重置(修改)DatePicker MonthPicker Cascader 的值

    1.清空值 this.props.form.resetFields(); 2.监听 props 的变化,触发 清空按钮 .