【本文为原创文章,转载自:blog.csdn.net/songrotek】
1 前言
当前有越来越多的可穿戴设备使用了蓝牙4.0 BLE(Bluetooth Low Energy)。对于iOS开发而言,Apple之前专门推出CoreBluetooth的Framework来支持BLE的开发。对于硬件开发有了解的朋友应该知道,在之前使用低版本的蓝牙的设备,要连接到iOS设备上,需要注册MFI,拥有MFI协议才能进行相应的开发。如果大家关注我之前对LEGO EV3的研究,就可以发现,EV3是使用了蓝牙2.1,因此需要MFI协议来进行开发。
本文将一步一步讲解如何使用CoreBluetooth框架来与各种可穿戴设备进行通信,使用 小米手环 来进行基本的测试。
2 开发环境
1 Macbook Pro Mac OS X 10.10
2 Xcode 6.3.2
3 iPhone 5s v8.1
4 小米手环
3 基本流程
要开发蓝牙,需要对整个通讯过程有个基本了解。这里我摘录一些Apple官方的文档Core Bluetooth Programming Guide的图片来加以说明。这个文档其实对于开发的流程写的是非常的清楚,大家最好可以看一下。
3.1 可穿戴设备与iOS互联方式
从上面这幅图可以看到,我们的iOS设备是Central,用来接收数据和发送命令,而外设比如小米手环是Peripheral,向外传输数据和接收命令。我们要做的就是通过Central来连接Peripheral,然后实现数据的接收和控制指令的发送。在做到这一步之后,再根据具体的硬件,对接收到的数据进行parse解析。
3.2 可穿戴设备蓝牙的数据结构
这里用的是心率设备来做说明,每个外设Peripheral都有对应的服务Service,比如这里是心率Service。一个外设可以有不止一个s、Service。每个service里面可以有多个属性Characteristic,比如这里有两个Characteristic,一个是用来测量心率,一个是用来定位位置。
那么很关键的一点是每个Service,每个Characteristic都是用UUID来确定的。UUID就是每个Service或Characteristic的identifier。
大家可以在iPhone上下载LightBlue这个应用。可以在这里查看一些设备的UUID。
在实际使用中,我们都是要通过UUID来获取数据。这点非常重要。
在CoreBluetooth中,其具体的数据结构图如下:
4 Step-By-Step 上手BLE开发
4.1 Step 1 创建CBCentralManager
从名字上大家可以很清楚的知道,这个类是用来管理BLE的。我们也就是通过这个类来实现连接。
先创建一个:
<code class="hljs objectivec has-numbering"><span class="hljs-keyword">@property</span> (<span class="hljs-keyword">nonatomic</span>,<span class="hljs-keyword">strong</span>) CBCentralManager *centralManager; <span class="hljs-built_in">dispatch_queue_t</span> centralQueue = dispatch_queue_create(<span class="hljs-string">"com.manmanlai"</span>, DISPATCH_QUEUE_SERIAL); <span class="hljs-keyword">self</span><span class="hljs-variable">.centralManager</span> = [[CBCentralManager alloc] initWithDelegate:<span class="hljs-keyword">self</span> queue:centralQueue];</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>
然后关键在于CBCentralManagerDelegate的使用。这个之后再讲。
4.2 Step 2 寻找CBPeripheral外设
有了CBCentralManager,接下来就是寻找CBPeripheral外设,方法很简单:
<code class="hljs ruby has-numbering">[<span class="hljs-keyword">self</span>.centralManager <span class="hljs-symbol">scanForPeripheralsWithServices:</span>@[] <span class="hljs-symbol">options:</span><span class="hljs-keyword">nil</span>];</code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>
这里的Service就是对应的UUID,如果为空,这scan所有service。
4.3 Step 3 连接CBPeripheral
在上一步中,如果找到了设备,则CBCentralManager的delegate会调用下面的方法:
<code class="hljs objectivec has-numbering">- (<span class="hljs-keyword">void</span>)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(<span class="hljs-built_in">NSDictionary</span> *)advertisementData RSSI:(<span class="hljs-built_in">NSNumber</span> *)RSSI { <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"name:%@"</span>,peripheral); <span class="hljs-keyword">if</span> (!peripheral || !peripheral<span class="hljs-variable">.name</span> || ([peripheral<span class="hljs-variable">.name</span> isEqualToString:@<span class="hljs-string">""</span>])) { <span class="hljs-keyword">return</span>; } <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">self</span><span class="hljs-variable">.peripheral</span> || (<span class="hljs-keyword">self</span><span class="hljs-variable">.peripheral</span><span class="hljs-variable">.state</span> == CBPeripheralStateDisconnected)) { <span class="hljs-keyword">self</span><span class="hljs-variable">.peripheral</span> = peripheral; <span class="hljs-keyword">self</span><span class="hljs-variable">.peripheral</span><span class="hljs-variable">.delegate</span> = <span class="hljs-keyword">self</span>; <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"connect peripheral"</span>); [<span class="hljs-keyword">self</span><span class="hljs-variable">.centralManager</span> connectPeripheral:peripheral options:<span class="hljs-literal">nil</span>]; } }</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li></ul>
我们在这里创建了一个CBPeripheral的对象,然后直接连接
CBPeripheral的对象也需要设置delegate.
4.4 Step 4 寻找Service
如果Peripheral连接成功的话,就会调用delegate的方法:
<code class="hljs objectivec has-numbering">- (<span class="hljs-keyword">void</span>)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { <span class="hljs-keyword">if</span> (!peripheral) { <span class="hljs-keyword">return</span>; } [<span class="hljs-keyword">self</span><span class="hljs-variable">.centralManager</span> stopScan]; <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"peripheral did connect"</span>); [<span class="hljs-keyword">self</span><span class="hljs-variable">.peripheral</span> discoverServices:<span class="hljs-literal">nil</span>]; } </code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li></ul>
我们这里先停止Scan,然后让Peripheral外设寻找其Service。
4.5 Step 5 寻找Characteristic
找到Service后会调用下面的方法:
<code class="hljs objectivec has-numbering">- (<span class="hljs-keyword">void</span>)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(<span class="hljs-built_in">NSError</span> *)error { <span class="hljs-built_in">NSArray</span> *services = <span class="hljs-literal">nil</span>; <span class="hljs-keyword">if</span> (peripheral != <span class="hljs-keyword">self</span><span class="hljs-variable">.peripheral</span>) { <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"Wrong Peripheral.\n"</span>); <span class="hljs-keyword">return</span> ; } <span class="hljs-keyword">if</span> (error != <span class="hljs-literal">nil</span>) { <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"Error %@\n"</span>, error); <span class="hljs-keyword">return</span> ; } services = [peripheral services]; <span class="hljs-keyword">if</span> (!services || ![services count]) { <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"No Services"</span>); <span class="hljs-keyword">return</span> ; } <span class="hljs-keyword">for</span> (CBService *service in services) { <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"service:%@"</span>,service<span class="hljs-variable">.UUID</span>); [peripheral discoverCharacteristics:<span class="hljs-literal">nil</span> forService:service]; } } </code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li></ul>
我们根据找到的service寻找其对应的Characteristic。
4.6 Step 6 找到Characteristic后读取数据
找到Characteristic后会调用下面的delegate方法:
<code class="hljs objectivec has-numbering">- (<span class="hljs-keyword">void</span>)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(<span class="hljs-built_in">NSError</span> *)error { <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"characteristics:%@"</span>,[service characteristics]); <span class="hljs-built_in">NSArray</span> *characteristics = [service characteristics]; <span class="hljs-keyword">if</span> (peripheral != <span class="hljs-keyword">self</span><span class="hljs-variable">.peripheral</span>) { <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"Wrong Peripheral.\n"</span>); <span class="hljs-keyword">return</span> ; } <span class="hljs-keyword">if</span> (error != <span class="hljs-literal">nil</span>) { <span class="hljs-built_in">NSLog</span>(@<span class="hljs-string">"Error %@\n"</span>, error); <span class="hljs-keyword">return</span> ; } <span class="hljs-keyword">self</span><span class="hljs-variable">.characteristic</span> = [characteristics firstObject]; <span class="hljs-comment">//[self.peripheral readValueForCharacteristic:self.characteristic];</span> [<span class="hljs-keyword">self</span><span class="hljs-variable">.peripheral</span> setNotifyValue:<span class="hljs-literal">YES</span> forCharacteristic:<span class="hljs-keyword">self</span><span class="hljs-variable">.characteristic</span>];</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li></ul>
这里我们可以使用readValueForCharacteristic:来读取数据。如果数据是不断更新的,则可以使用setNotifyValue:forCharacteristic:来实现只要有新数据,就获取。
4.7 Step 7 处理数据
读到数据后会调用delegate方法:
<code class="hljs r has-numbering">- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { NSData *data = characteristic.value; // Parse data <span class="hljs-keyword">...</span> } </code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul>
4.8 Step 8 向设备写数据
这个很简单,只要使用:
<code class="hljs lasso has-numbering"><span class="hljs-preprocessor">[</span><span class="hljs-built_in">self</span><span class="hljs-built_in">.</span>peripheral writeValue:<span class="hljs-built_in">data</span> forCharacteristic:<span class="hljs-built_in">self</span><span class="hljs-built_in">.</span>characteristic <span class="hljs-keyword">type</span>:CBCharacteristicWriteWithResponse<span class="hljs-preprocessor">]</span><span class="hljs-markup">;</span></code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>
data是NSData类型。
5 实验
使用小米手环实验,得到如下结果:
<code class="hljs perl has-numbering"><span class="hljs-number">2015</span>-<span class="hljs-number">06</span>-<span class="hljs-number">10</span> <span class="hljs-number">16</span>:<span class="hljs-number">52</span>:<span class="hljs-number">31.607</span> KetherDemo[<span class="hljs-number">13786</span>:<span class="hljs-number">1792995</span>] scaning device <span class="hljs-number">2015</span>-<span class="hljs-number">06</span>-<span class="hljs-number">10</span> <span class="hljs-number">16</span>:<span class="hljs-number">52</span>:<span class="hljs-number">33.474</span> KetherDemo[<span class="hljs-number">13786</span>:<span class="hljs-number">1793032</span>] name:<CBPeripheral: <span class="hljs-number">0x1700e4380</span>, identifier = <span class="hljs-number">6</span>FF833E3-<span class="hljs-number">93</span>C1-<span class="hljs-number">28</span>C6-CBC<span class="hljs-number">0</span>-<span class="hljs-number">74</span>A706AAAE31, name = LS_SCA16, <span class="hljs-keyword">state</span> = disconnected> <span class="hljs-number">2015</span>-<span class="hljs-number">06</span>-<span class="hljs-number">10</span> <span class="hljs-number">16</span>:<span class="hljs-number">52</span>:<span class="hljs-number">33.475</span> KetherDemo[<span class="hljs-number">13786</span>:<span class="hljs-number">1793032</span>] <span class="hljs-keyword">connect</span> peripheral <span class="hljs-number">2015</span>-<span class="hljs-number">06</span>-<span class="hljs-number">10</span> <span class="hljs-number">16</span>:<span class="hljs-number">52</span>:<span class="hljs-number">37.538</span> KetherDemo[<span class="hljs-number">13786</span>:<span class="hljs-number">1793031</span>] peripheral did <span class="hljs-keyword">connect</span> <span class="hljs-number">2015</span>-<span class="hljs-number">06</span>-<span class="hljs-number">10</span> <span class="hljs-number">16</span>:<span class="hljs-number">52</span>:<span class="hljs-number">37.984</span> KetherDemo[<span class="hljs-number">13786</span>:<span class="hljs-number">1793031</span>] service:FEE7 <span class="hljs-number">2015</span>-<span class="hljs-number">06</span>-<span class="hljs-number">10</span> <span class="hljs-number">16</span>:<span class="hljs-number">52</span>:<span class="hljs-number">37.985</span> KetherDemo[<span class="hljs-number">13786</span>:<span class="hljs-number">1793031</span>] service:Device Information <span class="hljs-number">2015</span>-<span class="hljs-number">06</span>-<span class="hljs-number">10</span> <span class="hljs-number">16</span>:<span class="hljs-number">52</span>:<span class="hljs-number">38.099</span> KetherDemo[<span class="hljs-number">13786</span>:<span class="hljs-number">1793032</span>] characteristics:( <span class="hljs-string">"<CBCharacteristic: 0x17409c250, UUID = FEC8, properties = 0x20, value = (null), notifying = NO>"</span>, <span class="hljs-string">"<CBCharacteristic: 0x17409c200, UUID = FEC7, properties = 0x8, value = (null), notifying = NO>"</span> ) <span class="hljs-number">2015</span>-<span class="hljs-number">06</span>-<span class="hljs-number">10</span> <span class="hljs-number">16</span>:<span class="hljs-number">52</span>:<span class="hljs-number">38.100</span> KetherDemo[<span class="hljs-number">13786</span>:<span class="hljs-number">1793032</span>] Kether did <span class="hljs-keyword">connect</span> <span class="hljs-number">2015</span>-<span class="hljs-number">06</span>-<span class="hljs-number">10</span> <span class="hljs-number">16</span>:<span class="hljs-number">52</span>:<span class="hljs-number">38.101</span> KetherDemo[<span class="hljs-number">13786</span>:<span class="hljs-number">1793032</span>] Kether did <span class="hljs-keyword">connect</span> <span class="hljs-number">2015</span>-<span class="hljs-number">06</span>-<span class="hljs-number">10</span> <span class="hljs-number">16</span>:<span class="hljs-number">52</span>:<span class="hljs-number">38.280</span> KetherDemo[<span class="hljs-number">13786</span>:<span class="hljs-number">1793031</span>] characteristics:( <span class="hljs-string">"<CBCharacteristic: 0x17009f270, UUID = Manufacturer Name String, properties = 0x2, value = (null), notifying = NO>"</span>, <span class="hljs-string">"<CBCharacteristic: 0x17009f2c0, UUID = Model Number String, properties = 0x2, value = (null), notifying = NO>"</span>, <span class="hljs-string">"<CBCharacteristic: 0x17009f310, UUID = Serial Number String, properties = 0x2, value = (null), notifying = NO>"</span>, <span class="hljs-string">"<CBCharacteristic: 0x17009eb90, UUID = Hardware Revision String, properties = 0x2, value = (null), notifying = NO>"</span>, <span class="hljs-string">"<CBCharacteristic: 0x17009f0e0, UUID = Firmware Revision String, properties = 0x2, value = (null), notifyi`` = NO>"</span>, </code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li></ul>
6 小结
通过上面的方法,我们就可以轻松的对BLE进行开发。实际上比想象的要简单。