iosswiftcore-bluetoothl2cap

How to send and receive data via CBL2CAPChannel


I'm trying to open an L2CAP channel between 2 iOS devices and transfer data both ways. One of the devices acts as a central the other one as a peripheral.

On the peripheral side:

I publish an L2CAPChannel like this:

func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
    if peripheral.state == .poweredOn {
        peripheral.publishL2CAPChannel(withEncryption: false)
    }
}

Tried both true and false for encryption.

Then once the channel is published I get the PSM from the didPublishL2CAPChannel delegate method and create a service with a characteristic containing the PSM as it's value and start advertising it.

On the central side:

I scan for peripherals, find the right one, connect to it, start discovering services, then once the service is discovered I discover characteristics. I find the characteristic, read it's value and get the PSM. Then I do this:

self.peripheral.openL2CAPChannel(psm)

Then I get a call back in the delegate method that the channel is open and do this:

func peripheral(_ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error?) {
    guard error == nil else {
        print("Couldn't open channel. Error: \(error!.localizedDescription)")
        return
    }
    self.l2capChannel = channel
    self.l2capChannel?.inputStream.delegate = self
    self.l2capChannel?.outputStream.delegate = self
    print("L2CAP channel opened with \(peripheral.name ?? "unknown")")
}

This prints:

L2CAP channel opened with mrsta’s iPad

On the peripheral side again:

I get the call back in the delegate method:

func peripheralManager(_ peripheral: CBPeripheralManager, didOpen channel: CBL2CAPChannel?, error: Error?) {
    guard error == nil else {
        print("Couldn't open channel. Error: \(error!.localizedDescription)")
        return
    }
    self.l2capChannel = channel
    self.l2capChannel?.inputStream.delegate = self
    self.l2capChannel?.outputStream.delegate = self
    print("L2CAP channel opened")
}

This prints:

[CoreBluetooth] No central present! Creating a new object. This shouldn't happen.
L2CAP channel opened

So far it seems like the channel is opened on both sides. I just wonder what's that message in the above print "... No central present!..."

After a while I start getting messages like this in the console:

[CoreBluetooth] WARNING: Unknown error: 436
[CoreBluetooth] No known channel matching peer <CBPeripheral: 0x2829de120, identifier = 241BAA6F-0BFD-9F5A-1EC9-35A4FD246DF5, name = mrsta’s iPad, state = connected> with psm 192
[CoreBluetooth] WARNING: Unknown error: 431

I have no idea what these mean. Any suggestions?

I've also implemented the StreamDelegate method on both sides:

func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
    print("Stream Event occurred: \(eventCode)")
    if eventCode == .hasSpaceAvailable {
        self.tryToWrite()
    }
}

But the above delegate method is never called. I try to write to the output stream like this (tryToWrite called from the didOpen channel delegate method on the central side):

func tryToWrite() {
    let string = "Hello"
    let stringData = Data(from: string)
    let _ = stringData.withUnsafeBytes { write(stuff: $0, to: self.l2capChannel, withMaxLength: stringData.count) }
}

func write(stuff: UnsafePointer<UInt8>, to channel: CBL2CAPChannel?, withMaxLength maxLength: Int) {
    let result = channel?.outputStream.write(stuff, maxLength: maxLength)
    print("Write result: \(String(describing: result))")
}

And the result is:

Write result: Optional(-1)

Which based on the documentation means that the write failed.

Please tell me what am I missing? What are those errors that I get after opening the channel and what is the right way to write and read data?


Solution

  • I use L2CAP and It is working. What I do in both "didOpen" functions is

    self.l2capChannel = channel
    
    self.l2capChannel?.inputStream.delegate = self 
    self.l2capChannel?.inputStream.schedule(in: .main, forMode: .defaultRunLoopMode)
    self.l2capChannel?.inputStream.open()
    
    self.l2capChannel?.outputStream.delegate = self
    self.l2capChannel?.outputStream.schedule(in: .main, forMode: .defaultRunLoopMode)
    self.l2capChannel?.outputStream.open()
    

    and when I don't need them anymore I closed them

    self.l2capChannel?.inputStream.close()
    self.l2capChannel?.inputStream.remove(from: .main, forMode: .defaultRunLoopMode)
    
    self.l2capChannel?.outputStream.close()
    self.l2capChannel?.outputStream.remove(from: .main, forMode: .defaultRunLoopMode)
    
    self.l2capChannel? = nil