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

主要内容

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

1. 创建外设管理器

首先你需要创建一个CBPeripheralManager 对象,通过CBPeripheralManagerinitWithDelegate: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,通过CBUUIDUUIDWithString:方法创建一个CBUUID对象,像这样
CBUUID *myCustomServiceUUID =
[CBUUID UUIDWithString:@"71DA3FD1-7E10-41C1-B16F-4430B506CDE7"];

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

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

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

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

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

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

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

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

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

  myService.characteristics = @[myCharacteristic];

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

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

 [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. 公布你的服务给监听的中心管理器

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

 [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 CBAdvertisementDataLocalNameKeyCBAdvertisementDataServiceUUIDsKey.

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

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

注意: 数据发布是基于最大努力的,由于带宽是有限并且多个app可能同时发布,更多信息参考CBPeripheralManagerstartAdvertising: 的注释.
'当你应用后台也可以进行发布行为,这个话题参照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)];

在你设置值之后,你需要告知远程的中心设置已经成功设置,通过明确调用CBPeripheralManagerrespondToRequest: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);
...

使用上面的方法作为一个开始发送更新中心设备数据的引子,接下获取特征的更新值并调用CBPeripheralManagerupdateValue: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:方法,如果你的数据不能通过一次通知传递过去,解决方案就是在中心设备一边,通过 CBPeripheralreadValueForCharacteristic:方法,来获取数据,这个方法可以取回整个数据.

外设流程流程图

蓝牙(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. [Java]在窗口界面上画出硬盘中图片文件

    利用类javax.swing.JPanel来在窗口界面上画图.图片文件通过javax.imageio.ImageIO类来获取. import java.awt.Graphics; import jav ...

  2. Linux自定义应用程序及其菜单图标

    在Linux桌面系统中,如果需要自己添加一个应用程序,如果是标准的bin, lib, share结构,我通常将其放在/usr/local/bin中.如果非这样,或者程序文件很多,易造成Linux系统目 ...

  3. WAF攻击与防御

    背景 对于腾讯的业务来说,有两个方面决定着WAF能否发挥效果,一个是合适处理海量流量的架构,另一个关键因素则是规则系统.架构决定着WAF能否承受住海量流量的挑战,这个在之前的篇章中简单介绍过(详情见主 ...

  4. Scriptable render pipeline unity

    https://www.youtube.com/watch?v=zbjkEQMEShM LWRP https://blogs.unity3d.com/cn/2018/02/21/the-lightwe ...

  5. TensorFlow------单层(全连接层)实现手写数字识别训练及测试实例

    TensorFlow之单层(全连接层)实现手写数字识别训练及测试实例: import tensorflow as tf from tensorflow.examples.tutorials.mnist ...

  6. LeetCode——3Sum & 3Sum Closest

    3Sum 题目 Given an array S of n integers, are there elements a,b,c in S such that a + b + c = 0? Find ...

  7. 跟着辛星一起用CSS美化商品列表

    说实话,近期对CSS的关注还是蛮多的,不为别的,仅仅是由于自己喜欢,感觉写CSS就像画家绘画一样,使用热情和激情去探索,没有了那份功利心,反而感觉是一种享受.特别有成就感,好啦,今天就分享一期自己用C ...

  8. jstat的用法

    转载:http://www.51testing.com/html/92/77492-203728.html 用以判断JVM是否存在内存问题呢?如何判断JVM垃圾回收是否正常?一般的top指令基本上满足 ...

  9. poj 2236 Wireless Network 【并查集】

    Wireless Network Time Limit: 10000MS   Memory Limit: 65536K Total Submissions: 16832   Accepted: 706 ...

  10. Mybatis错误:Result Maps collection already contains value for 。。。。

    解决方法 原因:xml文件中存在重名对象,保持名称不要一样即可正常启动.因为我再次使用逆向工程生成mapper接口和xml文件时,忘了删除原来的xml文件,新生成的与旧的同时出现旧重复了. 那么我们在 ...