背景
某智能硬件模擬程序冤狡,例如模擬
插卡
/拔卡
/加熱
等事件聊品, 采用的策略是使用模擬程序發(fā)送模擬命令
,APP端解析之后乡括,進行下一步業(yè)務(wù)邏輯肃廓。
問題
傳輸大一點的數(shù)據(jù)就會被截斷,也就是說每次傳輸?shù)臄?shù)據(jù)包大小很小(測試是一次155個诲泌,但是ble上說是20個盲赊,apple
的Demo
也是分包20個字節(jié))。
模擬命令
采用json
格式档礁,舉例:
{
"array": [
1,
2,
3
],
"boolean": true,
"null": null,
"number": 123,
"object": {
"a": "b",
"c": "d",
"e": "f"
},
"string": "Hello World"
}
結(jié)果明顯被截斷了:
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傳輸包大小限制角钩,主流做法還有直接操作字節(jié)的方式。但是對要傳輸復雜數(shù)據(jù)明顯用字節(jié)形式不夠用呻澜。
解決方案
藍牙肯定也可以傳輸較大的數(shù)據(jù)包递礼,例如圖片傳輸。應(yīng)該采用那種方案呢羹幸?在Apple Documents
里搜到一個叫BTLE
的Demo脊髓。
具體的核心理論就是分包
,也就是把一條命令
分成多次來發(fā)送栅受,而后最后組裝
一下即可将硝。
分包示例圖:
如上圖所示,
模擬命令
分成15個包傳輸過來恭朗,每個包大小固定位最大20個字符,第1-14包為模擬命令
數(shù)據(jù)依疼,第十五個包只發(fā)送了一個EOM
字符串痰腮,接收端
每接收到一個包則把這個數(shù)據(jù)追加到上一個包數(shù)據(jù)上,直到收到EOM
標識律罢,則把當前收到的所有數(shù)據(jù)進行解析膀值,解析完成就可以進行下一步業(yè)務(wù)處理了。
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
端截圖
收包關(guān)鍵代碼:
//更新特征值后(調(diào)用readValueForCharacteristic:方法或者外圍設(shè)備在訂閱后更新特征值都會調(diào)用此代理方法)
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"更新特征值時發(fā)生錯誤误辑,錯誤信息:%@",error.localizedDescription);
[self writeToLog:[NSString stringWithFormat:@"更新特征值時發(fā)生錯誤沧踏,錯誤信息:%@",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"]) {
//處理數(shù)據(jù)
[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(@"未發(fā)現(xiàn)特征值.");
[self writeToLog:@"未發(fā)現(xiàn)特征值."];
}
}
Peripheral
端
Demo
上的Peripheral
端截圖
發(fā)包關(guān)鍵代碼:
/** 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
很直觀的可以看到結(jié)果,本Demo
能夠展現(xiàn)出已收到巾钉。但是未處理未收到重發(fā)等翘狱,因為目前的產(chǎn)品無此業(yè)務(wù)需求。
Demo的Git
地址砰苍。