如何通过蓝牙(LE)将图像传输到桌面应用程序

时间:2019-05-29 22:13:25

标签: android ios ionic-framework bluetooth bluetooth-lowenergy

我们目前正在尝试将图像从移动设备(在本例中为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传输,我认为这将需要很长时间才能完成。现在,我想知道为什么通过蓝牙进行的数据传输受到限制。

3 个答案:

答案 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,那么您应该很高兴并确保您的连接使用所有这些功能。如果不是这样,那么至少启用其中之一,您仍将获得更高的性能。