iOS蓝牙开发笔记(LightBlue调试、大小端转换、进制转换)

本文记录下在项目开发过程中遇到的问题及解决问题使用的方法

1、用LightBlue调试(或者nRF Connect
2、找出特征的properties组成
3、数据的大小端转换
4、数据的进制转换
5、蓝牙数据传输的网络工具类

1、LightBlue

不管你是开发安卓还是iOS,在你还没写有关蓝牙的代码之前,你必须确认一件事:这个硬件是好使的。
所以你需要一个第三方的APP来调试确认。
工具有很多种,比如LightBlue、nRF Connect。
用法大同小异,下面以LightBlue为例。

下载,进入APP后,打开蓝牙,下拉刷新就可以看到附近的外设了,找到你们硬件工程师提供的外设名称,点击进入;

主页面

然后找到相应的UUID(大字体的是服务的UUID,可点击的UUID是服务的特征),会一同看到Properties属性值:比如 Read 、Write 、Write Without Response Notify等 。

外设页面

然后点击需要的服务UUID,如果是订阅特征的话,会有“Listen for notifications”按钮可以点击,如果点击不了就说明不是订阅特征,硬件的这个UUID是发不出数据的。
如果是写特征的话,会有 “Write new value”是向硬件写命令的。如控制开关等等。

特征页面

有LightBlue与硬件工程师一同调试硬件很方便,当问题出现时,你可以确认是硬件的问题,还是你自己代码的问题。

而且当我们作为iOS开发蓝牙硬件的时候,你的硬件工程师并不知道他提供给你的硬件的服务是什么,特征是什么,只会跟你说:“这个UUID是板子收数据的,这个UUID是板子发数据的”,所以我们需要找到这些具体的UUID,然后辨别哪个可以实现功能。


2、找出特征的properties组成

当我们打印一个特征时,会发现properties=0x14,我们还可以使用以下方法在实现蓝牙代理方法里找出特征的properties组成。

//找出特征的properties组成
//http://lecason.com/2015/08/19/Objective-C-Find-Conbine/
-(void)logCharacteristicProperties:(CBCharacteristicProperties)properties
{
    if (properties & CBCharacteristicPropertyBroadcast) {
        NSLog(@"CBCharacteristicPropertyBroadcast");
    }
    if (properties & CBCharacteristicPropertyRead) {
        NSLog(@"CBCharacteristicPropertyRead");
    }
    if (properties & CBCharacteristicPropertyWriteWithoutResponse) {
        NSLog(@"CBCharacteristicPropertyWriteWithoutResponse");
    }
    if (properties & CBCharacteristicPropertyWrite) {
        NSLog(@"CBCharacteristicPropertyWrite");
    }
    if (properties & CBCharacteristicPropertyNotify) {
        NSLog(@"CBCharacteristicPropertyNotify");
    }
    if (properties & CBCharacteristicPropertyIndicate) {
        NSLog(@"CBCharacteristicPropertyIndicate");
    }
    if (properties & CBCharacteristicPropertyAuthenticatedSignedWrites) {
        NSLog(@"CBCharacteristicPropertyAuthenticatedSignedWrites");
    }
    if (properties & CBCharacteristicPropertyExtendedProperties) {
        NSLog(@"CBCharacteristicPropertyExtendedProperties");
    }
    if (properties & CBCharacteristicPropertyNotifyEncryptionRequired) {
        NSLog(@"CBCharacteristicPropertyNotifyEncryptionRequired");
    }
    if (properties & CBCharacteristicPropertyIndicateEncryptionRequired) {
        NSLog(@"CBCharacteristicPropertyIndicateEncryptionRequired");
    }
}

3、数据的大小端转换

当你拿到了外设的数据,向服务器传的时候,你的后台开发java小伙伴可能会跟你说传过来的数据都不对劲,很大之类的,你可以做下数据的大小端转换试一试。

这是为什么呢?

在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如在C语言中,一个类型为int的变量x地址为0x100,那么其对应地址表达式&x的值为0x100。且x的四个字节将被存储在存储器的0x100, 0x101, 0x102, 0x103位置。

而存储地址内的排列则有两个通用规则。一个多位的整数将按照其存储地址的最低或最高字节排列。如果最低有效字节在最高有效字节的前面,则称小端序;反之则称大端序。在网络应用中,字节序是一个必须被考虑的因素,因为不同机器类型可能采用不同标准的字节序,所以均按照网络标准转化。

例如假设上述变量x类型为int,位于地址0x100处,它的十六进制为0x01234567,地址范围为0x100~0x103字节,其内部排列顺序依赖于机器的类型。大端法从首位开始将是:0x100: 01, 0x101: 23,..。而小端法将是:0x100: 67, 0x101: 45,..

网络传输一般采用大端序,也被称之为网络字节序,或网络序。IP协议中定义大端序为网络字节序。伯克利socket API定义了一组转换函数,用于16和32bit整数在网络序和本机字节序之间的转换。htonl,htons用于本机序转换到网络序;ntohl,ntohs用于网络序转换到本机序。

使用这些函数决定于你要从主机字节顺序(你的电脑上的)还是网络字节顺序转化。如果是"host",函数的第一个字母为"h",否则"network"就为"n"。函数的中间字母总是"to",因为你要从一个转化到另一个,倒数第二个字母说明你要转化成什么。最后一个字母是数据的大小,"s"表示short,"l"表示long。

htons() host to network short
htonl() host to network long
ntohs() network to host short
ntohl() network to host long

如果不想用c函数,用NSSwapHostIntToBig() NSSwapHostShortToBig() NSSwapHostLongToBig()等等也可以,看起来更加清晰明了,但其实里面也是封装的上面这些c函数。

//使用例子:
short number = 2;
number = ntohs(number);
number =  NSSwapHostShortToBig(number);

当你要传输的函数不仅仅是简单的short,int,long型,比如说是3字节类型的数据,需要以下自定义的大小端转换方法

//大端与小端互转
-(NSData *)dataTransfromBigOrSmall:(NSData *)data
{
    NSString *tmpStr = [self dataChangeToString:data];
    NSMutableArray *tmpArra = [NSMutableArray array];
    for (int i = 0 ;i<data.length*2 ;i+=2)
 {
        NSString *str = [tmpStr substringWithRange:NSMakeRange(i, 2)];
        [tmpArra addObject:str];
    }
    NSArray *lastArray = [[tmpArra reverseObjectEnumerator] allObjects];
    NSMutableString *lastStr = [NSMutableString string];
    for (NSString *str in lastArray)
 {
        [lastStr appendString:str];   
    }
    NSData *lastData = [self HexStringToData:lastStr];
    return lastData; 
}

-(NSString*)dataChangeToString:(NSData*)data
{
    NSString * string = [NSString stringWithFormat:@"%@",data];
    string = [string stringByReplacingOccurrencesOfString:@"<" withString:@""];
    string = [string stringByReplacingOccurrencesOfString:@">" withString:@""];
    string = [string stringByReplacingOccurrencesOfString:@" " withString:@""];
    return string;  
}

-(NSMutableData*)HexStringToData:(NSString*)str
{
    NSString *command = str;
    command = [command stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSMutableData *commandToSend= [[NSMutableData alloc] init];
    unsigned char whole_byte;
    char byte_chars[3] = {'\0','\0','\0'};
    int i;
    for (i=0; i < [command length]/2; i++) {
        byte_chars[0] = [command characterAtIndex:i*2];
        byte_chars[1] = [command characterAtIndex:i*2+1];
        whole_byte = strtol(byte_chars, NULL, 16);
        [commandToSend appendBytes:&whole_byte length:1];
    }
    return commandToSend;
}

查看本机是大端还是小端的方法:

    short int x;
    char x0,x1;
    x=0x1122;
    x0=((char*)&x)[0]; //低地址单元
    x1=((char*)&x)[1]; //高地址单元
    
    if (x0 == 0x11) {
        NSLog(@"大端");
    }
    else if (x0==0x22)
    {
        NSLog(@"小端");
    }

或者是这样:

    if (1 != htonl(1)) {
        //小端模式,作相应处理
        NSLog(@"小端");
    } else {
        //大端模式,作相应处理
        NSLog(@"大端");
    }

4、数据的进制转换

当我第一次从外设拿到数据,在- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error中直接打印出来,是被<>包裹的十六进制数据,而后台一般需要的是十进制的,比较好处理。
我试了几个函数,想直接把收到外设的值转为字符串,发现都不可以

1、为空

NSString *result = [[NSString alloc] initWithData:characteristic.value  encoding:NSUTF8StringEncoding];
NSLog(@"%@",result);

2、为空

 NSString *result = [NSString stringWithUTF8String:[characteristic.value bytes]];
NSLog(@"%@",result);

3、乱码+空

NSStringEncoding myEncoding = CFStringConvertEncodingToNSStringEncoding (kCFStringEncodingGB_18030_2000);
NSString *result=[[NSString alloc]initWithData:characteristic.value encoding:myEncoding];
NSLog(@"%@",result);

最终我的数据传输方案是在该代理方法中,将接收到外设的数据NSData转为十六进制,再转为十进制,然后大小端转换,最后转为NSData发给服务器。

下面列出我使用的进制转换工具:
//10进制int转NSData
-(NSData *)intToData:(int)i;

//NSData转int(10进制)
-(int)dataToInt:(NSData *)data;

//将NSData转化为16进制字符串
- (NSString *)convertDataToHexStr:(NSData *)data;

//16进制字符串转10进制int
- (int)hexNumberStringToNumber:(NSString *)hexNumberString;

//16进制字符串转NSData
- (NSData *)hexToBytes:(NSString *)str;

//普通字符串,转NSData
- (NSData *)stringToBytes:(NSString *)str;

//int转data
-(NSData *)intToData:(int)i
{
    NSData *data = [NSData dataWithBytes: &i length: sizeof(i)];
    return data;
}


//data转int
-(int)dataToInt:(NSData *)data
{
    int i;
    [data getBytes:&i length:sizeof(i)];
    return i;
}


/将NSData转化为16进制字符串
- (NSString *)convertDataToHexStr:(NSData *)data {
    if (!data || [data length] == 0) {
        return @"";
    }
    NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[data length]];
    
    [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
        unsigned char *dataBytes = (unsigned char*)bytes;
        for (NSInteger i = 0; i < byteRange.length; i++) {
            NSString *hexStr = [NSString stringWithFormat:@"%x", (dataBytes[i]) & 0xff];
            if ([hexStr length] == 2) {
                [string appendString:hexStr];
            } else {
                [string appendFormat:@"0%@", hexStr];
            }
        }
    }];
    return string;
}


//16进制字符串转10进制
- (int)hexNumberStringToNumber:(NSString *)hexNumberString
{
    NSString * temp10 = [NSString stringWithFormat:@"%lu",strtoul([hexNumberString UTF8String],0,16)];
    //转成数字
    int cycleNumber = [temp10 intValue];
    return cycleNumber;
}


//16进制字符转(不带0x),转NSData
-(NSData *)hexToBytes:(NSString *)str
{
    NSMutableData * data = [NSMutableData data];
    for (int i = 0; i+2 <= str.length; i+=2) 
{
        NSString * subString = [str substringWithRange:NSMakeRange(i, 2)];
        NSScanner * scanner = [NSScanner scannerWithString:subString];
        uint number;
        [scanner scanHexInt:&number];
        [data appendBytes:&number length:1];
    }
    return data.copy;
}


//普通字符串,转NSData
- (NSData *)stringToBytes:(NSString *)str
{
    return [str dataUsingEncoding:NSASCIIStringEncoding];
}

关于数据的进制转换网上一搜有很多,我也是整理一下我用到的,一般也就是十进制、十六进制、NSData的互转,希望对你有帮助。


5、蓝牙数据传输的网络工具类

我的服务器端要求数据是以二进制流的形式,一段时间一个包发给他,而不是json形式的字典,所以我自定义了网络工具类,用NSMutableData配置请求体,而不是直接用AFN了事。

#import <Foundation/Foundation.h>

@interface JDBLENetworkTool : NSObject

//请求体
@property (nonatomic, strong) NSMutableData *bodyData;

+ (instancetype)sharedInstance;
//拼3字节数据段
- (void)append3BytesDataWith:(int)number;
//拼2字节数据段
- (void)appendShortDataWith:(short)number;
//拼int型数据段
- (void)appendIntDataWith:(int)number;

- (void)postForPath:(NSString *)path
            success:(void (^) (NSDictionary *data))success
             failed:(void (^) (NSDictionary *failData))fail;

@end
#import "JDBLENetworkTool.h"
#import "OSZDataTool.h"

@implementation JDBLENetworkTool

+ (instancetype)sharedInstance
{
    static JDBLENetworkTool *tool = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        tool = [[JDBLENetworkTool alloc]init];
    });
    return tool;
}


- (void)append3BytesDataWith:(int)number
{
    Byte bytes[3];
    memcpy(bytes ,&number ,sizeof(bytes));
    NSData *contentData = [NSData dataWithBytes: &bytes length: sizeof(bytes)];
    NSData *bigData = [[OSZDataTool sharedTool] dataTransfromBigOrSmall:contentData];
    [self.bodyData appendData:bigData];
}


- (void)appendShortDataWith:(short)number
{
    number = ntohs(number);
    NSData *bigData = [NSData dataWithBytes: &number length: sizeof(number)];
    [self.bodyData appendData:bigData];
}

- (void)appendIntDataWith:(int)number
{
//    number =  NSSwapHostIntToBig(number);
    number = ntohl(number);
    NSData *bigData = [NSData dataWithBytes: &number length: sizeof(number)];
    [self.bodyData appendData:bigData];
}

- (void)postForPath:(NSString *)path
            success:(void (^) (NSDictionary *data))success
             failed:(void (^) (NSDictionary *failData))fail
{
    // 1. 获取服务器端口的地址
    NSURL *url = [NSURL URLWithString:path];
    //#warning:对于POST请求,必须手动设置其请求方法,因此要使用可变请求
    // 2. 创建请求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    // 3. 设置请求方式
    request.HTTPMethod = @"POST";
    [request setValue:@"application/octet-stream" forHTTPHeaderField:@"Content-Type"];
    // 4. 设置请求体
    request.HTTPBody = self.bodyData;
    
    [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    }] resume];
}

然后在didUpdateValueForCharacteristic这个代理方法中用这个网络工具类拼接请求体,达到一定数量时发送就可以了。

希望对你有帮助,给个喜欢吧:-D

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容