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

Ent*_*ain 8 android bluetooth ios bluetooth-lowenergy ionic-framework

我们目前正在尝试实现将图像从移动设备(在本例中为iPhone)传输到桌面应用程序的过程。我们已经尝试了蓝牙串行插件,该插件可在Android上正常运行,但在扫描桌面应用程序时未列出任何设备。

为了涵盖iOS支持(AFAIK iOS仅支持BluetoothLE),我们重新实现了桌面应用程序以使用BluetoothLE并表现为外围设备。另外,我们更改了Ionic应用程序以使用BLE插件

现在,BluetoothLE仅支持传输大小为20 Byte的包,而我们的映像大约为500kb。因此,我们显然可以将图像拆分为多个块,并使用以下函数进行传输(取自该要点):

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();
}
Run Code Online (Sandbox Code Playgroud)

对于我们来说,这仍然意味着我们将不得不发射大约2万5 千次的传输,我认为这将需要很长时间才能完成。现在,我想知道为什么通过蓝牙进行的数据传输受到限制。

Ste*_*cht 6

如果你想尝试 L2CAP,你可以像这样修改你的 Central 桌面应用程序:

private let characteristicUUID = CBUUID(string: CBUUIDL2CAPPSMCharacteristicString)
...
Run Code Online (Sandbox Code Playgroud)

然后广告发布一个 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)
Run Code Online (Sandbox Code Playgroud)

在你的

func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) {
Run Code Online (Sandbox Code Playgroud)

各自的

func peripheralManager(_ peripheral: CBPeripheralManager, didPublishL2CAPChannel PSM: CBL2CAPPSM, error: Error?) {
Run Code Online (Sandbox Code Playgroud)

提供 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)
}
Run Code Online (Sandbox Code Playgroud)

终于在

func peripheralManager(_ peripheral: CBPeripheralManager, didOpen channel: CBL2CAPChannel?, error: Error?) 
Run Code Online (Sandbox Code Playgroud)

打开一个输入流:

channel.inputStream.delegate = self
channel.inputStream.schedule(in: RunLoop.current, forMode: .default)
channel.inputStream.open()
Run Code Online (Sandbox Code Playgroud)

代表可能看起来像这样:

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 ...
}
Run Code Online (Sandbox Code Playgroud)

具有中心角色的 iOS 应用

假设您的 iOS 代码中有类似的内容:

func sendImage(imageData: Data) {
    self.manager = CBCentralManager(delegate: self, queue: nil)
    self.imageData = imageData
    self.bytesToWrite = imageData.count
    NSLog("start")
}
Run Code Online (Sandbox Code Playgroud)

然后你可以在你的 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)
    }   
}
Run Code Online (Sandbox Code Playgroud)

一旦您收到已打开频道的通知,请在其上打开输出流:

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()
Run Code Online (Sandbox Code Playgroud)

您提供的流委托可能如下所示:

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 ...
Run Code Online (Sandbox Code Playgroud)

2017 年有一个很酷的 WWDC 视频,Core Bluetooth 中的新功能,请参见此处https://developer.apple.com/videos/play/wwdc2017/712/

在 14:45 左右,它开始讨论 L2Cap 通道的工作方式。

28:47,Get the Most of the Core Bluetooth主题开始,详细讨论性能相关的东西。这可能正是你感兴趣的。

最后,在 37:59,您将看到以 kbps 为单位的各种可能的吞吐量。根据幻灯片上显示的数据,L2CAP + EDL(扩展数据长度)+ 15ms 间隔的最大可能速度为 394 kbps。


mua*_*f80 2

请看一下这个评论

以下片段摘自那里

ble.requestMtu(yourDeviceId, 512, () => {
  console.log('MTU Size ok.');
}, error => {
  console.log('MTU Size failed.');
});
Run Code Online (Sandbox Code Playgroud)

这表明您需要在连接后请求 Mtu,然后我认为您可以将消息分成 512 字节而不是 20 字节的块。

他们针对 Android 特定问题做了此操作