Как перенести изображения через Bluetooth (LE) в настольное приложение - PullRequest
7 голосов
/ 30 мая 2019

В настоящее время мы пытаемся осуществить передачу изображений с мобильного устройства (в данном случае с iPhone) в настольное приложение. Мы уже попробовали подключаемый модуль Bluetooth Serial , который отлично работает для Android, но не отображает никаких устройств при сканировании для нашего настольного приложения.

Чтобы покрыть поддержку iOS (AFAIK iOS поддерживает только BluetoothLE), мы повторно реализовали наше настольное приложение, чтобы использовать BluetoothLE и вести себя как периферийное устройство. Также мы изменили наше приложение Ionic для использования плагина BLE .

Теперь BluetoothLE поддерживает только передачу пакетов размером 20 байт, тогда как размер нашего изображения составляет около 500 КБ. Таким образом, мы могли бы явно разделить наше изображение на куски и передать его с помощью следующей функции (взято из 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();
}

Тем не менее для нас это будет означать, что нам придется запустить около 25k передач , которые, как я предполагаю, займут много времени. Теперь мне интересно, почему передача данных по Bluetooth так затруднена?

Ответы [ 3 ]

4 голосов
/ 08 июня 2019

Если вы хотите попробовать 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) для потоковых соединений Bluetooth):

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 ...

Есть отличное видео WWDC 2017 года, Что нового в Core Bluetooth , см. Здесь https://developer.apple.com/videos/play/wwdc2017/712/

Примерно в 14:45 начинается обсуждение работы каналов L2Cap.

В 28:47 начинается тема Максимум использования ядра Bluetooth , в которой подробно обсуждаются вопросы, связанные с производительностью. Это, вероятно, именно то, что вас интересует.

Наконец, в 37:59 вы увидите различные возможные пропускные способности в кбит / с. На основе данных, показанных на слайде, максимально возможная скорость с интервалом L2CAP + EDL (расширенная длина данных) + 15 мс составляет 394 кбит / с.

2 голосов
/ 07 июня 2019

Пожалуйста, посмотрите на этот комментарий

Следующий фрагмент взят оттуда

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

Предполагается, что вам нужно запросить Mtu после подключенияи тогда я думаю, что вы можете разбить ваше сообщение на куски по 512 байт, а не по 20 байт.

Они сделали это для специфичной для Android проблемы

1 голос
/ 08 июня 2019

Сначала я должен сказать, что уже есть тонны постов в блогах и вопросов и ответов по той же теме, поэтому, пожалуйста, сначала прочитайте их.

Если вы используете iPhone 7, у вас есть расширение длины данных LE. MTU по умолчанию также составляет 185 байтов, что означает, что вы можете отправлять уведомления или писать без команд ответа с 182 байтами полезной нагрузки. И, пожалуйста, убедитесь, что вы абсолютно не используете «Write With Response» или «Indications», поскольку это почти остановит передачу. Когда вы запускаете iOS в центральном режиме, вы ограничены интервалом соединения 30 мс. Использование более короткого интервала подключения может иметь преимущества, поэтому я бы посоветовал вам вместо этого запускать iOS в периферийном режиме, чтобы вы с центральной стороны могли установить интервал подключения чего-то короткого, скажем, 12 мс. Начиная с iPhone X и iPhone 8, вы также можете переключиться на PHY 2 Мбит / с, чтобы увеличить скорость передачи. Таким образом, чтобы ответить на ваш реальный вопрос, почему передача данных BLE затруднена: это не так, по крайней мере, если вы следуете передовой практике.

Вы также ничего не рассказали о системе, которая запускает ваше настольное приложение. Если он поддерживает 2 Мбит / с PHY, расширение длины данных LE и MTU не менее 185, то вы должны быть довольны и убедиться, что ваши соединения используют все эти функции. Если нет, вы все равно получите более высокую производительность, если включите хотя бы один из них.

...