EAAccessory support over Lighting Audio Module in iOS 11 is unstable

I have noticed various strange behaviours with the EAAccessory interface on iOS 11 (Betas 5 and 6 **update and now in the full production version 11.0.2 **)). I wonder if anyone else has had these issues? Everything worked OK on iOS 10.


I am communicating with an accessory over a lightning socket (via a LAM), and get the following behaviour:


1. Connecting to a device, EAAccessoryManager returns a connected EAAccessory (an EASession can be established, and data received, although this doesn't always happen), but then immediately disconnects from the EAAccessory, and then returns another connected EAAccessory.

2. After opening an EASession on this new EAAccessory, no communications to the device is possible (sending data over the OutputStream doesn't result in the data getting to the accessory)

3. On switching away from the app and back again, either:

a) the app crashes

or

b) the input stream reports endEncountered and the output stream just seems to stop working, it never reports hasSpaceAvailable again.


Some simplified code which illustrates this problem is below:


import UIKit

import ExternalAccessory


class ViewController: UIViewController, StreamDelegate {

var connectedAccessory: EAAccessory?

var eaSession: EASession?

let supportedProtocolName = "My.Supported.Protocol"


@IBOutlet weak var logTextView: UITextView!

@IBOutlet weak var sendButton: UIButton!


override func viewDidLoad() {

super.viewDidLoad()

log("loaded")

// Do any additional setup after loading the view, typically from a nib.

NotificationCenter.default.addObserver(

forName: NSNotification.Name.EAAccessoryDidConnect,

object: nil,

queue: nil,

using: accessoryConnected)


NotificationCenter.default.addObserver(

forName: NSNotification.Name.EAAccessoryDidDisconnect,

object: nil,

queue: nil,

using: accessoryDisconnected)


EAAccessoryManager.shared().registerForLocalNotifications()

// Maybe we're already connected

if let connectedAccessory = EAAccessoryManager.shared().connectedAccessories.first {

self.connectedAccessory = connectedAccessory

openSession()

}

}


private func accessoryConnected(_ notification: Notification) {

log("accessoryConnected")

for accessory in EAAccessoryManager.shared().connectedAccessories {

log("accessory: \(accessory)")

log("")

}

// Connect to the first one (only expect there to be one for this test)

self.connectedAccessory = EAAccessoryManager.shared().connectedAccessories.first

openSession()

}


private func openSession() {

guard let connectedAccessory = self.connectedAccessory else {

return

}

self.eaSession = EASession(

accessory: connectedAccessory,

forProtocol: self.supportedProtocolName)

openStream(eaSession?.inputStream)

openStream(eaSession?.outputStream)

}


private func openStream(_ stream: Stream?) {

stream?.delegate = self

stream?.schedule(in: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)

stream?.open()

}


public func stream(_ stream: Stream, handle streamEvent: Stream.Event) {

switch streamEvent {

case Stream.Event.openCompleted:

log("\(stream) openCompleted")

if (eaSession?.inputStream?.streamStatus == Stream.Status.open

&& eaSession?.outputStream?.streamStatus == Stream.Status.open) {

log("both input and output are open")

}

case Stream.Event.hasBytesAvailable:

log("hasBytesAvailable")

readAll()

case Stream.Event.hasSpaceAvailable:

log("hasSpaceAvailable")

case Stream.Event.errorOccurred:

log("\(stream) errorOccurred")

closeSession()

case Stream.Event.endEncountered:

log("\(stream) endEncountered")

closeSession()

default:

log("\(stream) default")

closeSession()

}

}


private func readAll() {

let bufferSize = 128

var numberOfBytesRead = 0

repeat {

var buffer = [UInt8](repeating: 0, count: bufferSize)

numberOfBytesRead = eaSession!.inputStream!.read(&buffer, maxLength: buffer.count)

if (numberOfBytesRead > 0) {

log("read:\(buffer)")

}

} while (numberOfBytesRead == 128)

}


@IBAction func sendDataButtonPressed(_ sender: UIButton) {

sendData()

}


private func sendData() {

let data: [UInt8] = [1, 0, 0, 0, 16, 0, 0, 0, 0, 3]

log("Sending: \(data)")

self.eaSession?.outputStream?.write(data, maxLength: data.count)

}


private func closeSession() {

closeStream(eaSession?.inputStream)

closeStream(eaSession?.outputStream)

eaSession = nil

}


private func closeStream(_ stream: Stream?) {

stream?.close()

stream?.remove(from: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)

stream?.delegate = nil

}


private func accessoryDisconnected(_ notification: Notification) {

log("accessoryDisconnected")

for accessory in EAAccessoryManager.shared().connectedAccessories {

log("accessory: \(accessory)")

log("")

}

guard let accessory = notification.userInfo?[EAAccessoryKey] as? EAAccessory else {

return

}

log("disconnected accessory: \(accessory)")

log("")

closeSession()

}


private func log(_ msg: String) {

self.logTextView.insertText("\(msg)\n")

scrollLogToEnd()

}


private func scrollLogToEnd() {

let stringLength = self.logTextView.text.characters.count

self.logTextView.scrollRangeToVisible(NSMakeRange(stringLength - 1, 0))

}


override func didReceiveMemoryWarning() {

super.didReceiveMemoryWarning()

// Dispose of any resources that can be recreated.

}

}

Replies

Hey, I am seeing this behavior as well. Did you ever find a work around?


Also, even after the initial connect -> disconnect -> reconnect, I am having issues where the connection will randomly disconnect. Are you seeing this as well?

Hi dpg42 again,

Seems we are experiencing NSStreamEventEndEncountered too when we put our app in background on iOS 11.0

This behavior doesn't happen on iOS 10.x and we currently are not able to figure it out.

Creating a new EASession and reopening the streams doesn't seems to work either.

Hi - sorry not to have replied earlier, we put this on ice until the production ios11 came out, and I've only just got around to checking again. Still seems broken. I see the same as you (NSStreamEventEndEncountered) and unfortunately have not found a workaround 😟

You can workaround problem 3 (Switching away from the app and back again either crashes the app or closes the session with NSStreamEventEndEncountered)


by adding a setting to your info.plist file:


<key>UIBackgroundModes</key>

<array>

<string>external-accessory</string>

</array>

Hi,


Do we have any solution for this? Sometimes it is getting connected. But sometimes it is not.

We are seeing the same behavior on ios 11.3.1

Did you find out any workaround or usefull informations on this subject ?


On our side we see a strange thing happening, we have a native app and a cordova app communicating with a mfi accessory.

The native app behaves like you mentionned and we see both streams reports endEncountered.

But the cordova app works fine... ??

The code for handling the mfi communication is exactly the same, but execute as a plugin.


Mat

Has anyone had any luck on a solution for this?

I am still seeing this issue with iOS 14.2, the app crashes sometimes an accessory disconnects by turning it off or removing the power.
Does anyone know how to fix this crash?


Hello @alfdef did you find any solution to fix this crash?