背景
某智能硬件模拟程序,例如模拟
插卡
/拔卡
/加热
等事件, 采用的策略是使用模拟程序发送模拟命令
,APP端解析之后,进行下一步业务逻辑。
问题
传输大一点的数据就会被截断,也就是说每次传输的数据包大小很小(测试是一次155个,但是ble上说是20个,apple
的Demo
也是分包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。
具体的核心理论就是分包
,也就是把一条命令
分成多次来发送,而后最后组装
一下即可。
分包示例图:
如上图所示,
模拟命令
分成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
端截图
收包关键代码:
//更新特征值后(调用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
端截图
发包关键代码:
/** 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
能够展现出已收到。但是未处理未收到重发等,因为目前的产品无此业务需求。
Demo的Git
地址。