iOS – BLE开发笔记

CB Means CoreBluetooth
Refercene To:About Core Bluetooth
The Core Bluetooth framework provides the classes needed for your iOS and Mac apps to communicate with devices that are equipped with Bluetooth low energy wireless technology.
意思是说CoreBluetooth库提供了必要的类来满足iOS和Mac的App与低耗能蓝牙设备进行数据交互。

Central and Peripheral Devices

使用BLE的时候,我们需要区分好什么是中心设备?什么是外围设备?以及两者在蓝牙技术中扮演的角色意义。

如图1-1所示,我们一般使用的手机、电脑、IPad是中心设备,他们需要蓝牙设备传过来的数据;而蓝牙设备、便携式设备等是外围设备,他们可以发送数据给中心设备。
Centrals Discover and Connect to Peripherals That Are Advertising(中心设备发现和连接外围设备是通过广播的方式进行)外围设备会广播数据信息。
我们来看看外围设备的数据组成
中心设备(Central)如何和外围设备(Peripherals)进行数据交互
当中心设备与外围设备连接成功之后,外围设备的数据通过服务和特征传过来,同样的中心设备可以修改和设置特征的值。

Local Centrals and Remote Peripherals

在iOS中,中心设备通过CBCentralManager对象来表示。主要作用:发现和连接远程外围设备。而外围设备使用CBPeripheral对象来表示。如下图1-4所示
而远程外围设备的数据格式主要通过CBServiceCBCharacteristic两个对象来表示,如下图1-5所示
当然除了有【本地中心设备 – 远程外围设备】的模式还有一种就是【本地外围设备 – 远程中心设备】,此时需要CBPeripheralManager和CBCentral对象。当然目前不需要。上述模式如下图所示

以及【本地外围设备 – 远程中心设备】的数据表示方式如下图所示
在BLE中,中心设备有一组需要完成的任务,主要有:
1 discovering and connecting to available peripherals (发现和连接外围设备)
2 exploring and interacting with the data that peripherals have to offer (完成和外围设备的数据交互)
再BLE中,外围设备同样有一组不同于中心设备需要完成的任务
1 publishing and advertising services (分发或者广播服务)
2 responding to read, write, and subscription requests from connected centrals (回复中心设备的各类请求)
下面我们说一下如何使用Core Bluetooth Framework完成最普通的BLE的中心设备功能

  • Start up a central manager object.
  • Discover and connect to peripheral devices that are advertising.
  • Explore the data on a peripheral device after you’ve connected to it.
  • Send read and write requests to a characteristic value of a peripheral’s service.
  • Subscribe to a characteristic’s value to be notified when it is updated.
  • 在程序中,我们如何完成这几步呢?

    iOS开发 – 静态库.a文件制作

    开发XCode工程选项如下所示

    我们选择静态库

    工程创建完成后如下图所示

    // .h
    #import 
    @interface MyPrint : NSObject
    + (void) print;
    @end
    
    // .m
    #import "MyPrint.h"
    
    @implementation MyPrint
    
    + (void) print {
        NSLog(@"测试输出");
    }
    
    @end
    

    添加头文件

    之后设置编译目标为8.0,这样向上兼容
    编译模拟器和真机版本的静态库,并进行合并,如下图所示

    合并

    bogon:Tool tomcomputer$ lipo -create ./Debug-iphone
    Debug-iphoneos/        Debug-iphonesimulator/ 
    bogon:Tool tomcomputer$ lipo -create ./Debug-iphoneos/libMyPrint.a  ./Debug-iphonesimulator/libMyPrint.a -output libMyPrint.a
    bogon:Tool tomcomputer$ 
    

    将合并的.a文件和.h声明文件加入到一个新项目中进行测试
    测试成功
    同理unity中使用的bundle文件和.so文件分别用Mac和Android可以进行编译出来使用

    Java代码翻写成OC样例

    Java代码

    // TranslateData
    public void translateData(byte[] data, int size) {
        for (int i = 0; i < size; i++) {
            byte b = data[i];
            if (head_count < HEAD_LENGTH) {
                switch (head_count) {
                    case 0:
                        if (b == HOST) {
                            head_count++;
                            commandBean.setHost(b);
                        } else {
                            head_count = 0;
                        }
                        break;
                    case 1:
                        commandBean.setCommand(b);
                        head_count++;
                        break;
                    case 2:
                        commandBean.setSequence(b);
                        head_count++;
                        break;
                    case 3:
                        commandBean.setOption(b);
                        head_count++;
                        break;
                    case 4:
                        commandBean.setLength(b);
                        head_count++;
                        data_count = 0;
                        break;
                    default:
                        break;
                }
            } else {
                if (data_count < commandBean.getLength()) {
                    temp[data_count++] = b;
                } else {
                    byte bcc = Common.bcc(temp, 0, commandBean.getLength());
                    if (bcc == b) {
                        commandBean.setData(temp);
                        doCommand(commandBean);
                    }
                    head_count = 0;
                    data_count = 0;
                }
            }
        }
    }
    // commandBean
    public class CommandBean
    implements Serializable
    {
        private byte command;
        private byte[] data;
        private byte host;
        private byte length;
        private byte option;
        private byte sequence;
        
        public byte getCommand()
        {
          return command;
        }
        
        public byte[] getData()
        {
          return data;
        }
        
        public byte getHost()
        {
          return host;
        }
        
        public int getLength()
        {
          return Common.byte2Int(length);
        }
        
        public byte getOption()
        {
          return option;
        }
        
        public byte getSequence()
        {
          return sequence;
        }
        
        public void setCommand(byte paramByte)
        {
          command = paramByte;
        }
        
        public void setData(byte[] paramArrayOfByte)
        {
          data = paramArrayOfByte;
        }
        
        public void setHost(byte paramByte)
        {
          host = paramByte;
        }
        
        public void setLength(byte paramByte)
        {
          length = paramByte;
        }
        
        public void setOption(byte paramByte)
        {
          option = paramByte;
        }
        
        public void setSequence(byte paramByte)
        {
          sequence = paramByte;
        }
    }
    // 有关调用方法
    translateData(buffer, bytes);
    

    我们来看看buffer的具体样例:
    a0780000 02040004
    a0800000 13140080 30005555 33b2ddd9 01400000 0000be92 4c
    a0800000 130d0080 30005555 33b2ddd9 01400000 0000be92 55
    a0800000 13120080 30005555 33b2ddd9 01400000 0000be92 4aa08000 00130500 803000bb bb33b2dd d9014000 00000083 3ac8
    a0800000 130e0080 3000bbbb 33b2ddd9 01400000 0000833a c3a08000 00131100 80300055 5533b2dd d9014000 000000be 9249
    我们用a0780000 02040004来进行举例

    // a0|78|00|00|02|04|0004| 我们这样来进行拆分
    // 首先我们先来翻写translateData的Objective-C
    #pragma mark -执行命令
    - (void) doCommand:(struct DataBean) cmd {
        // int result;
        switch (cmd.command) {
            case CMD_GET_VERSION:
                [TopToast showToptoastWithText:@"获得版本!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_ERROR:
                [TopToast showToptoastWithText:@"系统错误!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_READ_LABEL:
                [TopToast showToptoastWithText:@"读取标签!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_WRITE_LABEL:
                [TopToast showToptoastWithText:@"写入标签!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_INVENTORY:
                [TopToast showToptoastWithText:@"查询标签!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_CLEAR_CACHE:
                [TopToast showToptoastWithText:@"清除缓存!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_SET_OUTPUT:
                [TopToast showToptoastWithText:@"设置功率!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_GET_OUTPUT:
                [TopToast showToptoastWithText:@"获取功率!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_BATTERY:
                [TopToast showToptoastWithText:@"获取电量!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_CHARGING:
                // TODO
            break;
            default:
                break;
        }
    }
    
    #pragma mark- 数据解析
    - (void) translateData: (Byte[])data size:(long)size {
        for (int i = 0; i < size; i++) {
            Byte b = data[i];
            if (_head_count < HEAD_LENGTH) {
                switch (_head_count) {
                    case 0:
                        if (b == HOST) {
                            _head_count++;
                            _dataBean.host = b;
                        } else {
                            _head_count = 0;
                        }
                    break;
                    case 1:
                        _dataBean.command = b;
                        _head_count++;
                    break;
                    case 2:
                        _dataBean.sequence = b;
                        _head_count++;
                    break;
                    case 3:
                        _dataBean.option = b;
                        _head_count++;
                    break;
                    case 4:
                        _dataBean.length = b;
                        _head_count++;
                        _data_count = 0;
                    break;
                }
            } else {
                if (_data_count < _dataBean.length) {
                    _temp[_data_count++] = b;
                } else  {
                    // 暂时不进行BCC校验
                    _dataBean.data = _temp;
                    [self doCommand:_dataBean];
                    _head_count = 0;
                    _data_count = 0;
                }
            }
        }
    }
    

    IOS开发(3) – Block

    基本概念

    Block是一个C Level的语法以及运行时的一个特性,和标准C中的函数(函数指针)类似,但是其运行需要编译器和运行时支持,从IOS4.0开始就很好地支持Blocks。Blocks主要用在一些回调函数的用法上,2个对象通信需要一些回调的时候,比如说2个对象在运行,我们需要知道其中一个对象什么时候完成,我们就需要一个回调函数,之前我们用代理,但是有些小的地方用代理大材小用,Blocks可以用来做一些代理的很好地支撑,可以用来做边界或者一些地方的回调函数。
    本质上说:Blocks实质其实就是回调函数。

    IOS开发(2) – 函数与参数

    IOS奇葩的函数与参数

  • 多个参数的形式
  • (方法的数据类型)函数名:(参数1数据类型)参数1的数值的名字 参数2的名字: (参数2数据类型) 参数2值的名字 …;
    这里我们同样按照上一篇文章的形式来举例
    Java中多参数函数的写法如下:

    public void JavaMethod(int a1, String a2, byte[] a3, Object a4) {
        // TODO
        int a_1 = a1;
        String a_2 = a2;
        ...
    }
    // 调用
    this.JavaMethod(10, "HelloWorld", new byte[10], null);
    

    C++中多参数的写法如下

    int max(int a, int b);
    int max(int a, int b) {
        int ret = 0;
        if (a > b) {
    
        } else {
    
        }
        return ret;
    }
    int c = max(100, 200);
    

    Objective-C中

    // 定义
    /**
     * 测试函数
     */
    -(void) setKids: (NSString *)myOldestKidName secondKid: (NSString *) mySecondOldestKidName thirdKid: (NSString *) myThirdOldestKidName;
    // 实现
    -(void) setKids: (NSString *)myOldestKidName secondKid: (NSString *) mySecondOldestKidName thirdKid: (NSString *) myThirdOldestKidName
    
    {
        NSString* firstSon = myOldestKidName;
        NSString* secondSon = mySecondOldestKidName;
        NSString* thirdSon = myThirdOldestKidName;
    }
    // 调用
    [self setKids:@"firstSon" secondKid:@"secondSon" thirdKid:@"thirdSon"];
    

    截图如下:


    IOS开发(1) – Objective-C基础

    Objective-C

  • 函数比较
  • 以HelloWorld为例

    // Java
    public void helloWorld(bool ishelloworld) {  
        //干点啥  
    }
    // C++
    void HelloWorld(bool ishelloworld) {
    }
    // Objective-C
    -(void) HelloWorld:(BOOL)ishelloworld {
        // 干点啥
    }
    

    说明:
    前面带有减号(-) 的方法为实例方法,必须使用类的实例才可以调用的。
    对应的有(+)号, 代表是类的静态方法,不需要实例化即可调用。

  • 消息
  • 向对象发送信息,消息是ios的运行时环境特有的机制,和C++,Java下的类,或实例调用类或实例的方法类似。我这说的是类似,他们的机制实际上是有很大的差别。
    举例

    // Objective-C
    [object  message:param1 withParameter:param2];
    NSString *string;
    string = [[NSString alloc] initWithString:@"Hello"];
    // 转化Java和C++
    object.message();
    object.message(param1, param2);
    char* str;
    str = new string("Hello world");
    
  • Import
  • import "Class.h"
    import 
    import 
    // C++和Java类似include和import
    
  • Property和Synthesize
  • @property 声明用于自动创建property属性变量的getter和setter.
    @Synthesize声明实现了property属性变量的getter和setter.

  • 头文件函数定义
  • -(returnType)method  
    -(returnType)method:(dataType)param1  
    -(returnType)method:(dataType)param1 withParam:(dataType)param2
    // 类似C++
    returnType method();
    returnType method(param1);
    returnType method(param1, param2);
    
  • self
  • [self method];
    this.method();
    
  • 继承关系和接口实现
  • ClassA:ParentA  
    ClassA:ParentA  
    ClassA < Protocol >
    // C++和Java类似
    ClassA extends ParentA  
    ClassA extends ParentA implements interface  
    ClassA implements interface 
    

    objective-c的 Protocol和c++、java的接口类似.

  • 空指针
  • id obj = nil;
    NSString *hello = nil;
    // 类似Java和C++的空指针
    
  • id
  • objective-c的和C++里的(void*)类似