美文网首页iOS开发iOS之功能细节iOS开发杂货铺
ios蓝牙开发项目实战 -(附小米手环实例)

ios蓝牙开发项目实战 -(附小米手环实例)

作者: mark666 | 来源:发表于2016-07-24 17:36 被阅读5679次

    前言

    最近一直在开发关于蓝牙的功能,本来是不想写这一篇文章,因为网上关于ios蓝牙开发的文章实在太多了,成吨成吨的文章出现,但是很遗憾都只是一些皮毛,或者只是简单的介绍一下基本概念而已,对于一些小白可能还有很多很多疑惑,所以萌生了写一篇文章,并附上实际例子的demo,供即将项目中准备开发的伙伴参考。

    正文

    首先

    我们得明确一下很重要的几个概念
    1.当前ios中开发蓝牙所运用的系统库是<CoreBluetooth/CoreBluetooth.h>
    2.蓝牙外设必须为4.0及以上,否则无法开发,蓝牙4.0设备因为低耗电,所以也叫做BLE。
    3.CoreBluetooth框架的核心其实是两个东西,peripheral和central, 可以理解成外设和中心,就是你的苹果手机就是中心,外部蓝牙称为外设。
    4.服务和特征(service and characteristic):简而言之,外部蓝牙中它有若干个服务service(服务你可以理解为蓝牙所拥有的能力),而每个服务service下拥有若干个特征characteristic(特征你可以理解为解释这个服务的属性)。
    5.Descriptor(描述)用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的单位。
    6.跟硬件亲测,Ios蓝牙每次最多接收155字节的数据,安卓5.0以下最大接收20字节,5.0以上可以更改最大接收量,能达到500多字节。

    其次

    通过以上关键信息的解释,然后看一下蓝牙的开发流程:

    1. 建立中心管理者
    2. 扫描外设(discover)
    3. 连接外设(connect)
    4. 扫描外设中的服务和特征(discover)
      4.1 获取外设的services
      4.2 获取外设的Characteristics,获取Characteristics的值,
      获取Characteristics的Descriptor和Descriptor的值
    5. 与外设做数据交互(explore and interact)
    6. 断开连接(disconnect)

    具体代码

    1.创建一个中心管理者

    //只要一触发这句代码系统会自动检测手机蓝牙状态,你必须实现其代理方法,当然得添加<CBCentralManagerDelegate>
    CBCentralManager *theManager = [[CBCentralManager alloc]initWithDelegate:self queue:nil];
    
    //从这个代理方法中你可以看到所有的状态,其实我们需要的只有on和off连个状态
    - (void)centralManagerDidUpdateState:(CBCentralManager *)central{
            switch (central.state) {
                case CBCentralManagerStateUnknown:
                    NSLog(@">>>CBCentralManagerStateUnknown");
                    break;
                case CBCentralManagerStateResetting:
                    NSLog(@">>>CBCentralManagerStateResetting");
                    break;
                case CBCentralManagerStateUnsupported:
                    NSLog(@">>>CBCentralManagerStateUnsupported");
                    break;
                case CBCentralManagerStateUnauthorized:
                    NSLog(@">>>CBCentralManagerStateUnauthorized");
                    break;
                case CBCentralManagerStatePoweredOff:
                    NSLog(@">>>CBCentralManagerStatePoweredOff");
                    break;
                 case CBCentralManagerStatePoweredOn:
                    NSLog(@">>>CBCentralManagerStatePoweredOn");
                    break;   
                default:
                    break;
            }
    
    

    当发现蓝牙状态是开启状态,你就可以利用中央设备进行扫描外设,如果为关闭状态,系统会自动弹出让用户去设置蓝牙,这个不需要我们开发者关心。

    2.利用中心去扫描外设

    //两个参数为nil,默认扫描所有的外设,可以设置一些服务,进行过滤搜索
     [theManager scanForPeripheralsWithServices:nil options:nil];
    

    2.1当扫描到外设,触发以下代理方法

    在这里需要说明的是,
    一.当扫描到外设,我们可以读到相应外设广播信息,RSSI信号强度(可以利用RSSI计算中心和外设的距离)。
    二.我们可以根据一定的规则进行连接,一般是默认名字或者名字和信号强度的规则来连接。
    三.像我现在做的无钥匙启动车辆锁定车辆,就需要加密通讯,不能谁来连接都可以操作车辆,需要和外设进行加密通讯,但是一切的通过算法的校验都是在和外设连接上的基础上进行,例如连接上了,你发送一种和硬件约定好的算法数据,硬件接收到校验通过了就正常操作,无法通过则由硬件(外设)主动断开。

    //这里默认扫到MI,主动连接,当然也可以手动触发连接
    - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
        NSLog(@"扫描连接外设:%@ %@",peripheral.name,RSSI);
       
        if ([peripheral.name hasSuffix:@"MI"]) {
            //保存外设,并停止扫描,达到节电效果
            thePerpher = peripheral;
            [central stopScan];
           //进行连接
            [central connectPeripheral:peripheral options:nil];
        }
         
    }
    

    3.当连接到外设,会调用以下代理方法

    这里需要说明的是
    当成功连接到外设,需要设置外设的代理,为了扫描服务调用相应代理方法

    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
    
        NSLog(@"连接外设成功!%@",peripheral.name);
        [peripheral setDelegate:self];
        [peripheral discoverServices:nil];
    }
    
    //连接外设失败
    -(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
    {
    NSLog(@"连接到外设 失败!%@ %@",[peripheral name],[error localizedDescription]);
    }
    

    4.扫描外设中的服务和特征

    //扫描到服务
    -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
        if (error)
        {
            NSLog(@"扫描外设服务出错:%@-> %@", peripheral.name, [error localizedDescription]);
            return;
        }
        NSLog(@"扫描到外设服务:%@ -> %@",peripheral.name,peripheral.services);
        for (CBService *service in peripheral.services) {
            [peripheral discoverCharacteristics:nil forService:service];
        }
        NSLog(@"开始扫描外设服务的特征 %@...",peripheral.name);
    
    }
    //扫描到特征
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
        if (error)
        {
            NSLog(@"扫描外设的特征失败!%@->%@-> %@",peripheral.name,service.UUID, [error localizedDescription]);
            return;
        }
        
        NSLog(@"扫描到外设服务特征有:%@->%@->%@",peripheral.name,service.UUID,service.characteristics);
        //获取Characteristic的值
        for (CBCharacteristic *characteristic in service.characteristics){
        
      //这里外设需要订阅特征的通知,否则无法收到外设发送过来的数据
      [peripheral setNotifyValue:YES forCharacteristic:characteristic];
                
    //这里以小米手环为例,当我们定义好每个特征是干什么用的,我们需要读取这个特征的值,当特征值更新了会调用
    //- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error方法
    //需要说明的是UUID是硬件定义好给你,如果硬件也是个新手,那你可以先打印出所有的UUID,找出有用的
                //步数
                if ([characteristic.UUID.UUIDString isEqualToString:@"FF06"])
                {
                    [peripheral readValueForCharacteristic:characteristic];
                }
                
                //电池电量
                else if ([characteristic.UUID.UUIDString isEqualToString:@"FF0C"])
                {
                    [peripheral readValueForCharacteristic:characteristic];
                }
                
                else if ([characteristic.UUID.UUIDString isEqualToString:@"2A06"])
                {
                    //震动
                    theSakeCC = characteristic;
                }
    
            }
        }
        
        
    }
    
    //扫描到具体的值->通讯主要的获取数据的方法
    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
    {
        if (error) {
            NSLog(@"扫描外设的特征失败!%@-> %@",peripheral.name, [error localizedDescription]);
            return;
        }
        NSLog(@"%@ %@",characteristic.UUID.UUIDString,characteristic.value);
        if ([characteristic.UUID.UUIDString isEqualToString:@"FF06"]) {
            Byte *steBytes = (Byte *)characteristic.value.bytes;  
            int steps = bytesValueToInt(steBytes);
          
        }
        else if ([characteristic.UUID.UUIDString isEqualToString: @"FF0C"])
        {
            Byte *bufferBytes = (Byte *)characteristic.value.bytes;
            int buterys = bytesValueToInt(bufferBytes)&0xff;
            NSLog(@"电池:%d%%",buterys);
    
        }
        else if ([characteristic.UUID.UUIDString isEqualToString:@"2A06"])
        {
            Byte *infoByts = (Byte *)characteristic.value.bytes;
            //这里解析infoByts得到设备信息
     
        }
    
    }
    

    5.与外设做数据交互

    需要说明的是苹果官方提供发送数据的方法很简单,只需要调用下面的方法

    - (void)writeValue:(NSData *)data forCharacteristic:(CBCharacteristic *)characteristic type:(CBCharacteristicWriteType)type
    
    

    我们只需要在搜索每个服务的特征,记录这个特征,然后向这个特征发送数据就可以了。

    6.断开连接

    调用以下代码,需要说明的是中心断开与外设的连接。

    - (void)cancelPeripheralConnection:(CBPeripheral *)peripheral;
    

    以上呢是整个蓝牙的开发过程,系统提供的框架api就这么多,开发起来也不是很难,要是你认为这篇文章到这里就结束了,你就大错特错了,这篇文章的精华内容将从这里开始,由于公司项目的保密性,我不能以它为例,那我就以小米手环为实例,主要分享一下数据解析。

    精华部分

    1.当调用了以下代理方法的时候,我们需要处理接收到的数据

    - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error
    

    小米手环所定义的几个UUID如下:

    @"FF06" 这个UUID定义的是步数
    @"FF0C" 这个UUID定义的是电量
    @"2A06"这个UUID定义的是震动
    @"FF01"这个UUID定义的是相关的设备信息

    通过以上的UUID,我们可以读取到步数,电量,操作手环震动,并读取手环相应设备的信息,这里需要说明的是我并不清楚设备信息的具体协议,所以这里没法解析。

    if ([characteristic.UUID.UUIDString isEqualToString:STEP]) {
            Byte *steBytes = (Byte *)characteristic.value.bytes;  
            int steps = bytesValueToInt(steBytes);
            NSLog(@"步数:%d",steps);
        }
    

    当我们读到步数这个UUID时,我们可以拿到value,小米手环所定义的协议是4个字节,我们需要将4个字节转换为int 类型即可
    方法如下

    //4个字节Bytes 转 int
    unsigned int  bytesValueToInt(Byte *bytesValue) {
       
     unsigned int  intV;
        intV = (unsigned int ) ( ((bytesValue[3] & 0xff)<<24)
                                |((bytesValue[2] & 0xff)<<16)
                                |((bytesValue[1] & 0xff)<<8)
                                |(bytesValue[0] & 0xff));
        return intV;
    }
    

    需要说明的是这个方法是C语言的方法,采用位与运算,当然如果项目中需要另一种方式的转换,如:发过来两字节需要你转换为int,如果你不会转换,可以去网上搜索,我会在文章后附一些常用的转换方法。
    这里重点说明的是步数读取,剩余类似。

    2.当我们给外设发送数据时,我们需要跟硬件定协议,当然这是在开始项目之前做好的事情。

    小米手环协议中震动命令的触发,是向硬件发送一个10进制的 2
    这里需要说明的是我们发送数据给硬件一般是字节数组,然后将他转换为NSData发送。

          //这里为了严谨起见,需要判断外设和特征是否存在,如果存在发送数据
           if (thePerpher && theSakeCC) {
            Byte zd[1] = {2};
            NSData *theData = [NSData dataWithBytes:zd length:1];
            [thePerpher writeValue:theData forCharacteristic:theSakeCC type:CBCharacteristicWriteWithoutResponse];
    
        }
    
    

    这里需要再添加一点,如果协议要求你发ASCII码,例如‘SHAKE’,只需要这么处理

     if (thePerpher && theSakeCC) {
            Byte zd[] = {'S','H','A','K','E'};
            NSData *theData = [NSData dataWithBytes:zd length:1];
            [thePerpher writeValue:theData forCharacteristic:theSakeCC type:CBCharacteristicWriteWithoutResponse];
    
        }
    

    3.项目中实际开发的运用
    当我们面对实际开发时,我们不可能这么直接去在一个控制器中去写这么多代码,如果你说这没多少啊,那我无话可说了😄。。。当然有人会说运用三方库的啊,babyBluetooth在github上star还是挺高的,我的观点是没有必要去依赖所谓的三方库,有的三方库高度封装性会致使我们如果遇到错误时无法排查,所以三方库慎用,当然你可以参考一些Star很高的三方库,看大神的代码思想,有利于自己读写代码的能力。

    我的主要思路是封装一个单例类,封装好扫描的方法,读取数据的方法(一般是代理回调),发送指令(例如小米的震动)方法,而在读取数据中我们可以采用模型的思想,当收到蓝牙外设发送过来的数据时候,我们解析完后包装成模型通过代理回传过去,以后我们在控制器中每次拿到的都是模型数据,这样处理起来方便大大的。
    下面将小米手环demo附上,供需要的朋友参考学习,如果文章中我有什么没有说的很明白,或者什么疑惑可以留言。

    demo https://github.com/markdashi/MIBLE

    附常用转换方法

    @interface NSString (Extension)
    
    //16进制字符串转成int
    - (int)convertHexStringToINT;
    
    //16进制字符串转换为2进制的字符串
    - (NSString *)getBinaryByhex;
    @end
    
    @implementation NSString (Extension)
    //不考虑内存溢出
    - (int)convertHexStringToINT
    {
        UInt64 mac1 =  strtoul([self UTF8String], 0, 16);
        
        return [[NSString stringWithFormat:@"%llu",mac1] intValue];
    }
    
    - (NSString *)getBinaryByhex
    {
        NSMutableDictionary  *hexDic = [[NSMutableDictionary alloc] init];
        
        hexDic = [[NSMutableDictionary alloc] initWithCapacity:16];
        
        [hexDic setObject:@"0000" forKey:@"0"];
        
        [hexDic setObject:@"0001" forKey:@"1"];
        
        [hexDic setObject:@"0010" forKey:@"2"];
        
        [hexDic setObject:@"0011" forKey:@"3"];
        
        [hexDic setObject:@"0100" forKey:@"4"];
        
        [hexDic setObject:@"0101" forKey:@"5"];
        
        [hexDic setObject:@"0110" forKey:@"6"];
        
        [hexDic setObject:@"0111" forKey:@"7"];
        
        [hexDic setObject:@"1000" forKey:@"8"];
        
        [hexDic setObject:@"1001" forKey:@"9"];
        
        [hexDic setObject:@"1010" forKey:@"A"];
        
        [hexDic setObject:@"1011" forKey:@"B"];
        
        [hexDic setObject:@"1100" forKey:@"C"];
        
        [hexDic setObject:@"1101" forKey:@"D"];
        
        [hexDic setObject:@"1110" forKey:@"E"];
        
        [hexDic setObject:@"1111" forKey:@"F"];
        
        NSMutableString *binaryString=[[NSMutableString alloc] init];
        
        for (int i=0; i<[self length]; i++) {
            
            NSRange rage;
            
            rage.length = 1;
            
            rage.location = i;
            
            NSString *key = [self substringWithRange:rage];
            
            binaryString = (NSMutableString *)[NSString stringWithFormat:@"%@%@",binaryString,[NSString stringWithFormat:@"%@",[hexDic objectForKey:key]]];
            
        }
        
        return binaryString;
    }
    
    //NSData转换为16进制字符串,NSData的分类
    - (NSString *)dataToHexString
    {
        NSUInteger          len = [self length];
        char *              chars = (char *)[self bytes];
        NSMutableString *   hexString = [[NSMutableString alloc] init];
        
        for(NSUInteger i = 0; i < len; i++ )
            [hexString appendString:[NSString stringWithFormat:@"%0.2hhx", chars[i]]];
        
        return hexString;
    }
    
    

    2016.8.29补充

    由于项目中需要做关于后台持续扫描,类似于常见的蓝牙音箱,打开手机APP连接蓝牙音箱,播放音乐,当手机远离蓝牙音箱后,停止播放,当手机靠近的时候,蓝牙音箱又开始播放了,对于这中需求的实现我开始很困惑,蓝牙如何后台持续扫描呢,我尝试了很多方法是失败的,经过我多方面查询资料弄清楚如何实现这个需求:

    1.需要后台运行需申请后台权限


    勾选即可拥有后台权限,如果外设持续发送数据,APP端可以接收到数据。

    2.扫描时需指定serviceUUID,需外设广播出自己的SeviceUUID,APP端作为扫描的条件。

    这是苹果扫描方法的的官方解释:

    Applications that have specified the <code>bluetooth-central</code> background mode are allowed to scan while backgrounded, with two

    •                  caveats: the scan must specify one or more service types in <i>serviceUUIDs</i>, and the <code>CBCentralManagerScanOptionAllowDuplicatesKey</code>
      
    •                  scan option will be ignored.
      

    显而易见的说的很清楚,后台模式时蓝牙作为central,必须指定<i>serviceUUIDs</i>,scan option忽略。

    例子

    扫描方法:

    [self.centralManger scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:@"FEE0"],[CBUUID UUIDWithString:@"FEE7"]] options:nil];
    
    

    这样当在后台的时候是可以持续扫描的。

    3.当后台断开连接时候会调用系统方法

    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
    

    我们需要在这里设置自动重连,即可实现上述的需求。

    2016.10.19补充

    关于蓝牙需要做一些补充及修改,首先对于后台扫描时候,我们申请后台权限只需要申请


    权限申请

    如果两个都申请,如果提交安装包到APPStore,你会被拒绝

    If your app is meant to work with external hardware, supported protocols must be included in the UISupportedExternalAccessoryProtocols key in your app's Info.plist file - and the hardware's PPID # should be provided in the Review Notes field of your app in iTunes Connect.
    Additionally, your app must be authorized by MFi to use the desired hardware. If you are not yet in the MFi Program, you can enroll at MFi program.
    Please either revise your Info.plist to include the UISupportedExternalAccessoryProtocols key and update your Review Notes to include the PPID # - or remove the external-accessory value from the UIBackgroundModes key.

    这就是苹果给出的结论,开始我很百思不得解,难道苹果需要外设得到苹果的认证?其实不是的,因为多勾选了将苹果手机当做外设的权限,这时候苹果手机会有广播的能力,苹果为了综合考虑,需要充当连接角色的外设认证!所以去掉另一个权限即可。

    相关文章

      网友评论

      • 简书是不是要倒闭了:我用小米的二代测试,并没有连接!这是什么情况?有更新吗?
      • L一N:请问楼主,小米手环1代光感版能用吗?
        mark666:@L一N 没试过
      • 曲终叶落:你好,请教一下
        1、如果APP被杀了,还能不能收到外设的数据?
        2、如果APP在后台间隔时间很久收到外设的数据,APP会被杀死吗
        曲终叶落:@mark666 谢谢
        mark666:@曲终叶落 1.App 完全被杀死是不能接收数据的 2.App 后台间隔时间很久应用会被系统自动回收的
      • 曲终叶落:比较详细
      • Kaaaaai:楼主请问有没有关于蓝牙免提之类的资料,最近需要用到,但网上没有展开谈的,先谢过楼主了~
        mark666:@Kaaaaai 没接触过
        Kaaaaai:@mark666 就是HFP,用蓝牙设备打电话
        mark666:@Kaaaaai 蓝牙免提是什么?
      • 小暖风:APP杀进程后,来电话,小米手环震动是怎么实现的呀?APP杀进程后还能拦截到来电话的事件吗?然后 通知手环震动吗?
        mark666:@小暖风 不知道
      • T92:请教一下,我按三步走了,勾选后台模式、指定service扫描、断线重连 后,app在后台还是被系统kill了,请问一下蓝牙APP怎么才能一直在后台运行呢
        mark666:@jackfrued_徒弟 不可能,只要后台有数据传输就不可能被杀
        T92:@mark666 额,我是没0.1秒就会发送一次心跳,但是还是活不过10分钟,不知道什么原因
        mark666:@jackfrued_徒弟 只要一直有数据传输就不会程序被杀死
      • RiberWang:请问 我搜索的name怎么好多都为空呢
      • RiberWang:请问 我搜索的小米手环2的name这个怎么没有发现有叫MI的这个呢
      • 码客波锣:iOS设备可以连接安卓手机吗?可以连接其他安卓设备吗,比如机器人?
        mark666:@码客波L 没看明白你这句话什么意思
        码客波锣:@mark666 我现在扫描不到安卓设备(手机)如何连接呢?
        mark666:@码客波L 都可以
      • csii993:非常感谢这么详细的教程,不过有一个问题想请教您 ,如果蓝牙通过手机→设置→蓝牙连接的,不是通过app连接。该如果判断系统已经连接了一个蓝牙设备并且这个设备是自己需要连接的蓝牙设备呢?
      • 何必轻言流年:感谢 作者 受教了
      • 44d3387e09f3:小米二代好像都变了
      • Python数据分析实战:买一个小米手环,就能进行这些测试了吗
      • 请叫我Joker:我下载了你的demo,蓝牙连接小米手环以后自动断开,是什么原因?求解
        mark666:@码农弟弟 这是一代的
        mark666:@码农弟弟 这是一代的
      • 65d30ed3eda2:demo下载后运行,发现一只在扫描,扫描很久才连上
      • 克罗克达尔:十分感谢,帮助很大
      • 93f403aef69f:蓝牙连接的时候会自动断开,这是什么原因
        93f403aef69f:@mark666 就是连接之后,不点断开连接,自动就断开了,还想问您一下,通过UUID=2A37 2A39读取心率的值,是不是读不出来,我运行这个demo,也没有读到步数的值,求指教
        mark666:@Cherry111 连接时候怎么会自动断开?
      • HeavyCross:大神,上架被拒,说是让提供蓝牙低功耗,我要怎么提供????Your app declares support for bluetooth-central and bluetooth-peripheral in the UIBackgroundModes key in your Info.plist, but does not provide Bluetooth Low Energy functionality. The Bluetooth Background modes are for apps that communicate to other devices using Bluetooth Low Energy and the Core Bluetooth framework.
        mark666:你申诉一下,说明你什么地方需要后台接收数据
        HeavyCross:@mark666 我用到后台蓝牙接收硬件发来的消息,我要怎么去说明我有用到这个功能呢
        mark666:@焦润涛 provide Bluetooth Low Energy functionality
        这句话是说你开启了蓝牙后台的功能,但是APP中无相应的功能,意思是无后台运行的相应功能。
      • 81332f614f12:麻烦问下,小米手环2 和 小米手环一代,都一样的uuid吗?
      • TurboL:正学习这个,谢谢分享。
      • goyohol:iOS10 以上 蓝牙设备的回连出问题了!设备在关机、手机蓝牙关闭后再开启,会调用 retrievePheripheralsArr = [self.manager retrievePeripheralsWithIdentifiers:@[[CBUUID UUIDWithString:ancsUUID]]];方法,但却不会返回数据!
        以下是对上述方法的数组中数据获取:
        if (retrievePheripherals.count != 0)
        {
        for (CBPeripheral *p in retrievePheripherals)
        {
        if ([p.identifier.UUIDString isEqualToString:ancsUUID])
        {
        self.myPeripheral = p;
        [self.manager connectPeripheral:self.myPeripheral options:nil];
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isANCSPeripheral"];
        return;
        }
        }
        }
        goyohol:@mark666 好的,我再研究研究!谢谢哈!
        mark666:@goyohol 我的产品定义不是这样的,退出以后必须重新手动扫描,没有用到这个代理方法!!!
      • fireunicorn:补充说明里的功能需要怎么做,可以更新一下github么,谢谢~
      • 9449ac8cc6ba:项目比较赶,如果您看见了希望您快速回复我 谢谢
      • 9449ac8cc6ba:您好,我现在刚接触蓝牙手环项目,有个问题想请教您,172158320
      • kinmo:楼主是怎么知道小米手环硬件的协议?
        mark666:@July丶ye 本文只能展示这么多内容,希望有所帮助
        kinmo:@mark666 (⊙v⊙)嗯,是没什么关系.就是想学习一下,如果不方便也没关系.
        mark666:@July丶ye 这个好像跟本文没什么关系吧?
      • ca7508dad23b: “1.创建一个中心管理者 ”代码里少写了个on的状态,不过无伤大雅。~
        谢谢您
        mark666:@海的树 看的很仔细,谢谢,我修改一下
      • DDDDeveloper:太喜欢了,好人!
      • Lol刀妹:小米手环多少钱?
      • 凯文Kevin21:nice...棒棒哒
      • 春泥Fu: :kissing_heart: 棒棒哒!
      • 井湾村夫:不错,不错。

      本文标题:ios蓝牙开发项目实战 -(附小米手环实例)

      本文链接:https://www.haomeiwen.com/subject/zxuwjttx.html