iOS 11 Bluetooth Disconnection Issue with Zebra Printers

In late September, Apple officially released iOS 11. With the upgrade to iOS 11 on iOS devices, we’ve seen people experience Bluetooth disconnection to Zebra Bluetooth enabled printers. The common symptom is that the app on iOS 11 device can no longer print labels suddenly, even though the printer appears as "Connected" in the settings. The user had to “forget” and reconnect  the printer in settings to resume printing. However, printing stopped again after one or two labels.

 

Here's how to fix the disconnection issue! In iOS v11 and above, due to Apple’s requirement to use version 2 of the iPod Accessory Protocol (IAP) to manage Bluetooth connections, Zebra recommends that developers place a one second delay between when they close a Bluetooth connection and when they attempt to open another Bluetooth connection. If an application adopts the changes in this way, the app on iOS v11 will work well together with our printer, no matter what firmware version is on the printer.

 

We will update the readme file in our SDK to share this same info. Stay tuned for more info!

 

[Latest Update on 2/6/2018]: Apple has released iOS version v11.2.5. Our internal testing has indicated that v11.2.5 appears to have addressed the Bluetooth connection/disconnection issues previously seen in prior releases of iOS v11. We recommend that developers experiencing issues with Bluetooth disconnections in iOS v11 move their iOS devices to v11.2.5.

Comments


would it be possible to provide a sample application that shows this fix?  Is a sample containing this fix already within the SDK sample?  If so, which project/module?  A code snippet would be helpful; even though it might seems straight forward.

thanks,


Hi Matthew,

As already documented in the release notes of Link-OS SDK for iOS v1.5.1049, Apple requires to use version 2 of iPod Accessory Protocol (IAP) to manage Bluetooth connections in iOS 11 and beyond. Due to this requirement, applications that connect with Zebra Bluetooth enabled printers on iOS 11 devices need further ensure that the Bluetooth connection is open and stays open if Bluetooth data transmission is still going on. If apps don't follow this guideline, it would manifest into various undesired behaviors, ended with the same EAAccessory Departure message as shown below in device log. Once we see this message, the app won't be able to re-open the Bluetooth connection even the printer appears connected in settings, unless we forget and re-pair with the printer.

CoreAccessories accessoryInfo for departure = {

    ACCExternalAccessoryPrimaryUUID = "A0D9D23F-0DBF-4946-8396-F2E0FFD36158";

    IAPAppAccessoryCapabilitiesKey = 1;

    IAPAppAccessoryDockTypeKey = "";

    IAPAppAccessoryFirmwareRevisionKey = 001;

    IAPAppAccessoryFirmwareRevisionPendingKey = "<null>";

    IAPAppAccessoryHardwareRevisionKey = 001;

    IAPAppAccessoryMacAddressKey = "AC:3F:A4:00:0D:DD";

    IAPAppAccessoryManufacturerKey = "Zebra Technologies";

    IAPAppAccessoryModelNumberKey = ZD410;

    IAPAppAccessoryNameKey = 50J153200124;

    IAPAppAccessoryProtocolsKey =     {

        "com.zebra.rawport" = 90;

    };

    IAPAppAccessorySerialNumberKey = 50J153200124;

    IAPAppAccessoryVehicleInfoInitialDataKey =     {

    };

    IAPAppConnectionIDKey = 37125317;

}

Here is an example of how to reproduce the EAAccessory Departure error and how to prevent it from happening. We use the Connectivity module in ZSDKDeveloperDemos project that comes with the release of Link-OS SDK for iOS v1.5.1049 for this example. To reproduce the problem, we add a loop to print out 100 labels inside performConnectionDemo: function. Depending on the size of data sent through

[connection write:data error:&error], the EAAccessory Departure error would occur before all 100 labels being printed out.

- (void) performConnectionDemo {

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

   

    self.performingDemo = YES;

    [self.testButton setEnabled:NO];

   

    [self.connectivityViewController setStatus:@"Connecting..." withColor:[UIColor yellowColor]];

    id<ZebraPrinterConnection, NSObject> connection = nil;

   

    if(self.connectivityViewController.isBluetoothSelected) {

        connection = [[[MfiBtPrinterConnection alloc] initWithSerialNumber:self.connectivityViewController.bluetoothPrinterLabel.text] autorelease];

    } else {

        NSString *ipAddress = [self.connectivityViewController.ipDnsTextField text];

        NSString *portAsString = [self.connectivityViewController.portTextField text];

        int port = [portAsString intValue];

        connection = [[[TcpPrinterConnection alloc] initWithAddress:ipAddress andWithPort:port] autorelease];

    }

    // Take out the preset delays from the connection to make the reproduction of the problem simplier.

    [connection setTimeToWaitBeforeCloseInMilliseconds:0]; // Last value was 105

    [connection setTimeToWaitAfterReadInMilliseconds:0];

    [connection setTimeToWaitAfterWriteInMilliseconds:0];

   

    id<ZebraPrinter,NSObject> printer = nil;

    BOOL didOpen = [connection open]; // Open the connection

   

    if(didOpen == YES) {

        [self.connectivityViewController setStatus:@"Connected..." withColor:[UIColor greenColor]];

           

        [self.connectivityViewController setStatus:@"Determining Printer Language..." withColor:[UIColor yellowColor]];

           

        NSError *error = nil;

        printer = [ZebraPrinterFactory getInstance:connection error:&error];

           

        if(printer != nil) {

               

            PrinterLanguage language = PRINTER_LANGUAGE_ZPL; // Preset the printer language to ZPL on the printer

               

            [self.connectivityViewController setStatus:@"Sending Data" withColor:[UIColor cyanColor]];

               

            id<GraphicsUtil, NSObject> graphicsUtil = [printer getGraphicsUtil];

               

            for (int j = 0; j < 100; j++) { // Print 100 labels in one open connection

                NSString *testLabel = @"^XA^FO20, 10^BY2^BEN,70,Y,N^FD8033609249442^FS^XZ";

                NSData *data = [NSData dataWithBytes:[testLabel UTF8String] length:[testLabel length]];

                [connection write:data error:&error];

            } // For-loop for j

               

        } else {

            [self.connectivityViewController setStatus:@"Could not Detect Language" withColor:[UIColor redColor]];

        }

    } else {

        [self.connectivityViewController setStatus:@"Could not connect to printer" withColor:[UIColor redColor]];

    }

   

    [self.connectivityViewController setStatus:@"Disconnecting..." withColor:[UIColor redColor]];

   

//    // Checking the print status before closing the connection

//    NSError *error = nil;

//    PrinterStatus *status = nil;

//    do {

//        status = [printer getCurrentStatus:&error];

//        if (status == nil) {

//            NSLog(@"MyLog - getCurrentStatus returns nil");

//        } else if (status.isReceiveBufferFull) {

//            NSLog(@"MyLog - Receive Buffer is full");

//        } else if (status.numberOfFormatsInReceiveBuffer) {

//            NSLog(@"MyLog - There are still %ld jobs in receive buffer", status.numberOfFormatsInReceiveBuffer);

//        }

//

//        sleep (1); // Delay by 1 sec. for another iteration of checking

//    } while (status == nil || status.numberOfFormatsInReceiveBuffer);

   

    [connection close]; // Close the connection

   

    self.performingDemo = NO;

   

    [self.connectivityViewController setStatus:@"Not Connected" withColor:[UIColor redColor]];

   

    [self.testButton setEnabled:YES];

    [pool release];

}

If we uncomment the block of code above to check the printer status and to make sure there is no print job in the receive buffer before we close the Bluetooth connection, then we would not see the EAAccessory Departure error. This is just one example of the timing things. In essences, we cannot solely rely on the return of connection.write() as an indication that the data has been sent to the printer. The return of connection.write() only means that the data has been written to the Bluetooth send buffer successfully. Therefore, we need always to make sure that all data has been transmitted to the printer before closing a connection, and make sure that the connection is truly closed, before open another connection.

Hope this helps.


Steven, thank you for the example.  I'm trying to deal with a similar problem myself, but I wanted to be sure I understood how the error arose in the above example.

In your performConnectionDemo method, you open the connection, then perform the 100 labels loop, and finally close the connection.  Specifically, you are not closing the connection during the loop at all, yet you say that the departure event would happen before the end of the loop.  Since you are implying that the trouble starts when the connection is closed before all the data is flushed, I would have to conclude that your test actually consists of running the performConnectionDemo method multiple times, and that when the error occurred during the loop, it was actually due to a premature closing on the previous call to performConnectionDemo.  Am I understanding this correctly?

Also, would I be right in assuming that the 1 second sleep in the status checking loop is not necessary for the solution, but that the important thing is to not close the connection until the printer reports 0 for numberOfFormatsInReceiveBuffer?

In our test app, what we are doing is opening a connection which we never close.  Nevertheless, on iOS 11, after some random number of minutes, we will get a disconnection event, after which we can no longer see the printer unless we "forget" and "reconnect" with it in the iPad Settings/Bluetooth screen.  So since we get the disconnect event eventually, even though we don't close our connection (and therefore presumably don't prematurely lose buffered data), it makes me think there's some other cause involved.

Thoughts?


Thank you for your response.  You indicated that iOS 11 made some Bluetooth timing changes.  Based on that, do you recommend any changes to the Zebra SDK default values of the 3 setTimeToWait* parameters when running on iOS 11?


Steven, thank you for your very helpful comments. In answer to your question, I have two similar test apps, one which uses the latest Zebra SDK, and one which uses only the Apple frameworks. They both exhibit the same problem of disconnecting after a random number of minutes, even though the app never closes the connection at all unless it gets a disconnection. It typically happens after about 15-25 minutes. This wouldn’t be a big deal if we could simply reconnect to the device, but just as in the case of the app prematurely closing the connection, we can no longer reconnect without going through the manual “forget” & reconnect steps. I’d be happy to send you the sources for the test app if you’d like to investigate further.


Hi Michael,

In the above example, the departure event happened before all 100 labels being printed out by the printer. Timing wise, the departure event happened after the completion of loop for 100 labels and when the [connection close] got executed. That's why we recommend to add a block of code to check the printerStatus before closing. We only need to run  performConnectionDemo method once to reproduce the problem in the above example. Sorry for the confusion. I should have made it clear in the description.

The purpose of the delay in the loop of printer status check is to avoid hogging the CPU time. What happens with [printer getCurrentStatus:&error] is that it may return nil if the data channel is busy in transmitting label data. So I threw in 1 second delay here for illustration. The delay can be any other values. Indeed, the important purpose to get the printer status and make sure there is no pending print job, i.e. numberOfFormatsInReceiveBuffer is zero.

In our test app, what we are doing is opening a connection which we never close.  Nevertheless, on iOS 11, after some random number of minutes, we will get a disconnection event, after which we can no longer see the printer unless we "forget" and "reconnect" with it in the iPad Settings/Bluetooth screen.  So since we get the disconnect event eventually, even though we don't close our connection (and therefore presumably don't prematurely lose buffered data), it makes me think there's some other cause involved.

In your case, do you use the API from Zebra SDK for your application? If we use the API from SDK, we should not see the above issue. If you use the API of Zebra SDK, there are other parameters in Bluetooth connection can be adjusted to adapt to different Bluetooth radio performance requirements.

- (void) setTimeToWaitAfterReadInMilliseconds:(NSInteger) aTimeInMs

- (void) setTimeToWaitAfterWriteInMilliseconds:(NSInteger) aTimeInMs

- (void) setTimeToWaitBeforeCloseInMilliseconds:(NSInteger) aTimeInMs

If you use your own mechanism to communicate the iOS Bluetooth module, then you can add some debug log in your open and close methods. So you can gauge the timing when the departure event happens. Hope that helps on debugging.


Hi Michael,

The 3 setTimeToWait* API are designed for applications to adjust the wait time for read, write and close operations on a connection to achieve a desired performance level. These wait times are not affected directly by the timing changes at the OS level in iOS 11. The current defaults of the wait times are 10ms, 60ms and 5,000ms for read, write and close operations respectively. Shorter wait time results in greater throughput or better performance.

For printing a batch of labels, let's take the above example of 100 labels, the shorter wait time after each write operation would mean the data of 100 labels would be transmitted quicker to the printer and the printer would print out the labels quicker with less delay between the labels. This gets interesting. If we could set the wait time of close operation long enough to get through all the data of 100 labels being actually printed at the printer side, then the departure event may not happen. Then the question is how long of the wait time is long enough? If each label has different amount of data (depending on the content of labels, such as ZPL, graphics, etc), then the estimated wait time is problematic. So the safest way is to check the printer status before closing the connection.


Hi Michael,

One more thing I forgot to mention early was that we also have observed the same departure event then using the GC Dispatch Queue to send labels through the Bluetooth connection. We should avoid using dispatch queue to send labels on the Bluetooth connection, if we cannot ensure that there is only one label at a time. Generally, this is hard to guarantee when using dispatch queue, especially with asynchronous.

We will see the same departure event happen, if we do so when each label consists of a large amount of data. For example, if we replace the code between line 45 and line 47 inside the loop of 100 labels with a function that uses dispatch queue to print the label, we would almost hit the departure event immediately. So we should treat the Bluetooth connection as singular and avoid using multi-thread to send large amount of data simultaneous. Hope this would solve most of the problems.


Hi Steven,

I also tried to implement your solution. However, when I set the print mode to CPCL line_print, the "status = [printer getCurrentStatus:&error];" returns nil, which means I can't test if the print is complete before closing the connection, and that the loop you gave would run forever. I tried to change the print mode to ZPL, and got a return value all right. The content printed out, even though the formatting kind of lost. Could you please tell me why the status returned is 'nil' when I use CPCL line_print mode, and what can I do to fix it, so that I can check the status before I close the Bluetooth connection?

Thank you!


Steven,

I am working to implement your recommendations from Nov. 13th.  I am hoping you can clarify why you wrote:

while (status == nil || status.numberOfFormatsInReceiveBuffer)

instead of:

while (status == nil || status.isReceiveBufferFull || status.numberOfFormatsInReceiveBuffer)

Isn't status.isReceiveBufferFull a reason to not close the connection?

Thanks,

Del


Hi Del,

Thanks for bringing up a very good point. The the status.isReceiveBufferFull is YES, it well indicates that there could be some un-transmitted packets remaining on the host. Therefore the connection should remain open. I agree with you to add status.isReceiveBufferFull in the condition check. 


Hi Jianghua,

When [printer getCurrentStatus:&error] returns nil, you can check the error object to see what the exact error is. It’s probable @"Malformed status response - unable to determine printer status". This is because the getCurrentStatus: is not designed to work in line_print mode. If we look at the API documentation for ZebraPrinterFactory and PrinterLanguage, we will see that the SDK only supports PRINTER_LANGUAGE_ZPL and PRINTER_LANGUAGE_CPCL. You need to change the language mode to CPCL instead of line_print mode. You can specify the language mode through getInstance:withPrinterLanguage: API in ZebraPrinterFactory​ in the SDK. The Best Practices also documents on how to set the language mode in SDK and on the printer.

Hope this will solve the problem.


Hi Steven,

I am facing the same problem as described above , getting @"Malformed status response - unable to determine printer status" during checking printer status.

- We are using latest zebra sdk (V1.5.1049)

- Printer Model: ZEBRA iMZ320

- iOS version 11.2.1

As I learn, I don't need to set printer language specifacally for iMZ320 as getInstance method will put the printer ZPL mode. Then why SOMETIMES we are getting this error during checking Printer status?

I have put a code sample and details description here:

Could you please let us know your feedback as we are strugling with this issue after IOS 11 release.

In advance thanks.


This doesn't sound like an issue in Print Language setting. It's more like a timeout or corrupted response data. See my comments in iMZ320 in iOS:Sometimes getCurrentStatus shows ErrorCode=7


I've the same problem on Xamarin.Forms Application. Printing is ok in IOS 11.2.1 but never work in IOS 11.2.2.

The error is "malformed status responde" on CheckPrinterStatus method.

My printer is Zebra IMZ320. I use LinkOS.Plugin version 1.1.75

How i can fix? I'm sure that only one thread is open and only 1 ipad is connect with printer.


Hi Nicola,

Could you please put your comment in below link:

I have the same problem that I am discussing with Steven (Zebra developer)

In advance thanks,

Molay


Dear Zebra-Dev-Team,

is there any chance that the your Framework will take responsibility of managing the proposed delay?

I guess that every iOS developer is facing this problem and I don't think that every client should implement this.

I also noticed that there hasn't been an update for the iOS SDK in a while. Is there an update on its way?

On your website I can only find v1.5.1049.

Thanks in advance and best regards,

Benedikt


is there any chance that the your Framework will take responsibility of managing the proposed delay?

I guess that every iOS developer is facing this problem and I don't think that every client should implement this.

The above is a good point. We will bring this to the engineering team for consideration.

Yes, v1.5.1049 is the latest as of today for iOS. It was released in Oct, 2017. We don't have update on any future releases.