BLE传输大数据

背景

某智能硬件模拟程序,例如模拟插卡/拔卡/加热等事件, 采用的策略是使用模拟程序发送模拟命令,APP端解析之后,进行下一步业务逻辑。

问题

传输大一点的数据就会被截断,也就是说每次传输的数据包大小很小(测试是一次155个,但是ble上说是20个,appleDemo也是分包20个字节)。

模拟命令采用json格式,举例:

{
    "array": [
              1,
              2,
              3
              ],
    "boolean": true,
    "null": null,
    "number": 123,
    "object": {
        "a": "b",
        "c": "d",
        "e": "f"
    },
    "string": "Hello World"
}

结果明显被截断了:

2017-05-16 16:50:26.417340 GlucometerTestApp[65247:10879249] 收到特征更新通知...
2017-05-16 16:50:30.353523 GlucometerTestApp[65247:10879249] 读取到特征值:{

    "array": [

              1,

              2,

              3

              ],

    "boolean": true,

    "null": null,

    "number": 123,
2017-05-16 16:50:30.405110 GlucometerTestApp[65247:10879249] 读取到特征值:EOM
2017-05-16 16:50:30.430806 GlucometerTestApp[65247:10879249] didReceiveData:(null)

因为ble传输包大小限制,主流做法还有直接操作字节的方式。但是对要传输复杂数据明显用字节形式不够用。

解决方案

蓝牙肯定也可以传输较大的数据包,例如图片传输。应该采用那种方案呢?在Apple Documents里搜到一个叫BTLE的Demo。
具体的核心理论就是分包,也就是把一条命令分成多次来发送,而后最后组装一下即可。
分包示例图:

packge.png

如上图所示,模拟命令分成15个包传输过来,每个包大小固定位最大20个字符,第1-14包为模拟命令数据,第十五个包只发送了一个EOM字符串,接收端每接收到一个包则把这个数据追加到上一个包数据上,直到收到EOM标识,则把当前收到的所有数据进行解析,解析完成就可以进行下一步业务处理了。

Demo分包传输日志如下:

2017-05-16 17:03:44.832831 GlucometerTestApp[65291:10885464] 收到特征更新通知...
2017-05-16 17:03:57.853364 GlucometerTestApp[65291:10885464] 读取到特征值:{

    "array": [
2017-05-16 17:03:57.861861 GlucometerTestApp[65291:10885464] 读取到特征值:             1,
2017-05-16 17:03:57.869106 GlucometerTestApp[65291:10885464] 读取到特征值:           2,
2017-05-16 17:03:57.908184 GlucometerTestApp[65291:10885464] 读取到特征值:         3
2017-05-16 17:03:57.923027 GlucometerTestApp[65291:10885464] 读取到特征值:      ],

    "boole
2017-05-16 17:03:57.932465 GlucometerTestApp[65291:10885464] 读取到特征值:an": true,

    "nul
2017-05-16 17:03:57.939771 GlucometerTestApp[65291:10885464] 读取到特征值:l": null,

    "numb
2017-05-16 17:03:57.946587 GlucometerTestApp[65291:10885464] 读取到特征值:er": 123,

    "obje
2017-05-16 17:03:57.953552 GlucometerTestApp[65291:10885464] 读取到特征值:ct": {

        "a":
2017-05-16 17:03:57.959731 GlucometerTestApp[65291:10885464] 读取到特征值: "b",

        "c":
2017-05-16 17:03:57.965410 GlucometerTestApp[65291:10885464] 读取到特征值:"d",

        "e": "
2017-05-16 17:03:57.971610 GlucometerTestApp[65291:10885464] 读取到特征值:f"

    },

    "str
2017-05-16 17:03:57.977920 GlucometerTestApp[65291:10885464] 读取到特征值:ing": "Hello World"
2017-05-16 17:03:57.987575 GlucometerTestApp[65291:10885464] 读取到特征值:
}
2017-05-16 17:03:57.994471 GlucometerTestApp[65291:10885464] 读取到特征值:EOM
2017-05-16 17:03:58.001022 GlucometerTestApp[65291:10885464] didReceiveData:{
    array =     (
        1,
        2,
        3
    );
    boolean = 1;
    null = "<null>";
    number = 123;
    object =     {
        a = b;
        c = d;
        e = f;
    };
    string = "Hello World";
}

Central

Demo上的Central端截图

ScreenShot_20170516174707.png

收包关键代码:

//更新特征值后(调用readValueForCharacteristic:方法或者外围设备在订阅后更新特征值都会调用此代理方法)
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    if (error) {
        NSLog(@"更新特征值时发生错误,错误信息:%@",error.localizedDescription);
        [self writeToLog:[NSString stringWithFormat:@"更新特征值时发生错误,错误信息:%@",error.localizedDescription]];
        return;
    }
    if (characteristic.value) {
        NSString *value=[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
        NSLog(@"读取到特征值:%@",value);
        [self writeToLog:[NSString stringWithFormat:@"读取到特征值:%@",value]];
            if ([value isEqualToString:@"EOM"]) {
                //处理数据
                [self.delegate didReceiveData:self.data complate:YES];
                
                //处理完毕,清空
                [self.data setLength:0];
            }
        else
        {
            [self.data appendData:characteristic.value];
            if(self.delegate)
            {
                [self.delegate didReceiveData:self.data complate:NO];
            }
        }
    }else{
        NSLog(@"未发现特征值.");
        [self writeToLog:@"未发现特征值."];
    }
}

Peripheral

Demo上的Peripheral端截图

ScreenShot_20170516174355.png

发包关键代码:

/** Sends the next amount of data to the connected central
 */
- (void)_sendData
{
    // First up, check if we're meant to be sending an EOM
    static BOOL sendingEOM = NO;
    
    if (sendingEOM) {
        
        // send it
        BOOL didSend = [self.peripheralManager updateValue:[@"EOM" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.characteristicM onSubscribedCentrals:nil];
        
        // Did it send?
        if (didSend) {
            
            // It did, so mark it as sent
            sendingEOM = NO;
            
            NSLog(@"Sent: EOM");
        }
        
        // It didn't send, so we'll exit and wait for peripheralManagerIsReadyToUpdateSubscribers to call sendData again
        return;
    }
    
    // We're not sending an EOM, so we're sending data
    
    // Is there any left to send?
    
    if (self.sendDataIndex >= self.dataToSend.length) {
        
        // No data left.  Do nothing
        return;
    }
    
    // There's data left, so send until the callback fails, or we're done.
    
    BOOL didSend = YES;
    
    while (didSend) {
        
        // Make the next chunk
        
        // Work out how big it should be
        NSInteger amountToSend = self.dataToSend.length - self.sendDataIndex;
        
        // Can't be longer than 20 bytes
        if (amountToSend > NOTIFY_MTU) amountToSend = NOTIFY_MTU;
        
        // Copy out the data we want
        NSData *chunk = [NSData dataWithBytes:self.dataToSend.bytes+self.sendDataIndex length:amountToSend];
        
        // Send it
        didSend = [self.peripheralManager updateValue:chunk forCharacteristic:self.characteristicM onSubscribedCentrals:nil];
        
        // If it didn't work, drop out and wait for the callback
        if (!didSend) {
            return;
        }
        
        NSString *stringFromData = [[NSString alloc] initWithData:chunk encoding:NSUTF8StringEncoding];
        NSLog(@"Sent: %@", stringFromData);
        
        // It did send, so update our index
        self.sendDataIndex += amountToSend;
        
        // Was it the last one?
        if (self.sendDataIndex >= self.dataToSend.length) {
            
            // It was - send an EOM
            
            // Set this so if the send fails, we'll send it next time
            sendingEOM = YES;
            
            // Send it
            BOOL eomSent = [self.peripheralManager updateValue:[@"EOM" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:self.characteristicM onSubscribedCentrals:nil];
            
            if (eomSent) {
                // It sent, we're all done
                sendingEOM = NO;
                
                NSLog(@"Sent: EOM");
            }
            
            return;
        }
    }
}

Demo

具体直接运行Demo很直观的可以看到结果,本Demo能够展现出已收到。但是未处理未收到重发等,因为目前的产品无此业务需求。
DemoGit地址。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 蓝牙简介 蓝牙( Bluetooth® ):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离...
    Chefil阅读 2,027评论 2 19
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 前言: 检测密码 邮箱 IP地址 手机号输入是否正确 已经封装好了,拖入工程就可以使用! //邮箱正则表达式 + ...
    少年_如他阅读 988评论 1 2
  • 今晚的活动是绘画,我和女儿各自一副。 花边是女儿加上的。她的进步是竟然颜色都没有溢出,没有框在固定思维里,色彩多,...
    淼淼的妈妈阅读 163评论 0 0