我们目前正在尝试将图像从移动设备(在本例中为IPhone)传输到桌面应用程序。我们已经尝试过Bluetooth Serial plugin,该版本在Android上可以正常使用,但在扫描桌面应用程序时不会列出任何设备。
为涵盖iOS支持(AFAIK iOS仅支持BluetoothLE),我们重新实现了桌面应用程序以使用BluetoothLE并表现为外围设备。另外,我们将Ionic应用程序更改为使用BLE plugin。
现在,BluetoothLE仅支持传输大小为20字节的软件包,而我们的映像大约为500kb。因此,我们显然可以将图像拆分为多个块,然后使用以下函数(取自this gist)进行传输:
function writeLargeData(buffer) {
console.log('writeLargeData', buffer.byteLength, 'bytes in',MAX_DATA_SEND_SIZE, 'byte chunks.');
var chunkCount = Math.ceil(buffer.byteLength / MAX_DATA_SEND_SIZE);
var chunkTotal = chunkCount;
var index = 0;
var startTime = new Date();
var transferComplete = function () {
console.log("Transfer Complete");
}
var sendChunk = function () {
if (!chunkCount) {
transferComplete();
return; // so we don't send an empty buffer
}
console.log('Sending data chunk', chunkCount + '.');
var chunk = buffer.slice(index, index + MAX_DATA_SEND_SIZE);
index += MAX_DATA_SEND_SIZE;
chunkCount--;
ble.write(
device_id,
service_uuid,
characteristic_uuid,
chunk,
sendChunk, // success callback - call sendChunk() (recursive)
function(reason) { // error callback
console.log('Write failed ' + reason);
}
)
}
// send the first chunk
sendChunk();
}
对于我们而言,这仍然意味着我们必须启动大约<25,000传输,我认为这将需要很长时间才能完成。现在,我想知道为什么通过蓝牙进行的数据传输受到限制。
答案 0 :(得分:4)
如果要试用L2CAP,可以按以下方式修改Central桌面应用程序:
private let characteristicUUID = CBUUID(string: CBUUIDL2CAPPSMCharacteristicString)
...
然后广告并发布L2CAP频道:
let service = CBMutableService(type: peripheralUUID, primary: true)
let properties: CBCharacteristicProperties = [.read, .indicate]
let permissions: CBAttributePermissions = [.readable]
let characteristic = CBMutableCharacteristic(type: characteristicUUID, properties: properties, value: nil, permissions: permissions)
self.characteristic = characteristic
service.characteristics = [characteristic]
self.manager.add(service)
self.manager.publishL2CAPChannel(withEncryption: false)
let data = [CBAdvertisementDataLocalNameKey : "Peripherial-42", CBAdvertisementDataServiceUUIDsKey: [peripheralUUID]] as [String : Any]
self.manager.startAdvertising(data)
在您的
func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
分别是您的
func peripheralManager(_ peripheral: CBPeripheralManager, didPublishL2CAPChannel PSM: CBL2CAPPSM, error: Error?) {
提供PSM值(=用于蓝牙流连接的套接字句柄(UInt16)的种类):
let data = withUnsafeBytes(of: PSM) { Data($0) }
if let characteristic = self.characteristic {
characteristic.value = data
self.manager.updateValue(data, for: characteristic, onSubscribedCentrals: self.subscribedCentrals)
}
最终在
func peripheralManager(_ peripheral: CBPeripheralManager, didOpen channel: CBL2CAPChannel?, error: Error?)
打开输入流:
channel.inputStream.delegate = self
channel.inputStream.schedule(in: RunLoop.current, forMode: .default)
channel.inputStream.open()
代表可能看起来像这样:
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case Stream.Event.hasBytesAvailable:
if let stream = aStream as? InputStream {
...
//buffer is some UnsafeMutablePointer<UInt8>
let read = stream.read(buffer, maxLength: capacity)
print("\(read) bytes read")
}
case ...
}
具有中心角色的iOS应用
假设您的iOS代码中包含类似内容:
func sendImage(imageData: Data) {
self.manager = CBCentralManager(delegate: self, queue: nil)
self.imageData = imageData
self.bytesToWrite = imageData.count
NSLog("start")
}
然后,您可以在iOS客户端上修改外围设备以使用L2Cap通道,如下所示:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
...
if let characteristicValue = characteristic.value {
let psm = characteristicValue.withUnsafeBytes {
$0.load(as: UInt16.self)
}
print("using psm \(psm) for l2cap channel!")
peripheral.openL2CAPChannel(psm)
}
}
并在收到打开频道的通知后,立即在其上打开输出流:
func peripheral(_ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error?)
...
channel.outputStream.delegate = self.streamDelegate
channel.outputStream.schedule(in: RunLoop.current, forMode: .default)
channel.outputStream.open()
您提供的流委托可能看起来像这样:
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case Stream.Event.hasSpaceAvailable:
if let stream = aStream as? OutputStream, let imageData = self.imageData {
if self.bytesToWrite > 0 {
let bytesWritten = imageData.withUnsafeBytes {
stream.write(
$0.advanced(by: totalBytes),
maxLength: self.bytesToWrite
)
}
self.bytesToWrite -= bytesWritten
self.totalBytes += bytesWritten
print("\(bytesWritten) bytes written, \(bytesToWrite) remain")
} else {
NSLog("finished")
}
}
case ...
2017年有一个很酷的WWDC视频,核心蓝牙中的新功能,请参见此处https://developer.apple.com/videos/play/wwdc2017/712/
在14:45左右开始讨论L2Cap通道的工作方式。
在28:47,开始充分利用核心蓝牙主题,其中详细讨论了与性能有关的事情。这可能正是您感兴趣的。
最后,在37:59,您将看到以kbps为单位的各种可能的吞吐量。 根据幻灯片上显示的数据,L2CAP + EDL(扩展数据长度)+ 15ms间隔的最大可能速度为394 kbps。
答案 1 :(得分:2)
请看看这个comment
以下摘录摘自
ble.requestMtu(yourDeviceId, 512, () => {
console.log('MTU Size ok.');
}, error => {
console.log('MTU Size failed.');
});
这建议您需要在连接后请求Mtu,然后我认为您可以将消息分成512字节而不是20字节的块。
他们已经针对特定于Android的issue
答案 2 :(得分:1)
首先,我应该说已经有大量关于完全相同主题的博客文章和问答,所以请先阅读它们。
如果运行iPhone 7,则具有LE数据长度扩展。默认的MTU也是185字节,这意味着您可以发送通知或写入,而无需响应命令,负载为182字节。并且请确保您绝对不使用带有响应的写或指示,因为那样会几乎阻止传输。当您在中央模式下运行iOS时,连接间隔被限制为30毫秒。使用较短的连接间隔可能会有好处,因此我建议您改为在外围设备模式下运行iOS,以便您从中央侧可以将连接间隔设置得较短,例如12 ms。从iPhone X和iPhone 8开始,您还可以切换到2MBit / s PHY,以提高传输速度。因此,要回答您的实际问题,为什么BLE数据传输会受到限制:至少在遵循最佳实践的情况下,才不会。
您还没有告诉任何有关运行桌面应用程序的系统的信息。如果它支持2 MBit / s PHY,LE数据长度扩展和至少185的MTU,那么您应该很高兴并确保您的连接使用所有这些功能。如果不是这样,那么至少启用其中之一,您仍将获得更高的性能。