Printing from Android to a Bluetooth Zebra printer without pairing

I'm looking for a way to print from an Android device to a Zebra printer over bluetooth without previously pairing it.

 

The printer does not have an authentication PIN so pairing seems to have issues with the Android interface: https://code.google.com/p/android/issues/detail?id=26041

 

I've done some testing using the createInsecureRfcommSocketToServiceRecord API with mixed results. Using below code I'm able to print on the MZ320 from Android v4.1.2 devices but the same program fails on Android v4.4.3. So far I've tested:

 

  • MC40 JB v4.1.2 - Works
  • TC55 JB v4.1.2 - Works
  • TC70 KK v4.4.3 - Printer lights up blue led signaling a Bluetooth request but no printing
  • Nexus 7 (2012) KK v4.4.3 - Printer lights up blue led signaling a Bluetooth request but no printing

 

The only difference in the logs running the app is that I get this warning on KK devices:

11-06 12:10:05.824    2136-2178/com.pietromaggi.sample.printing W/BluetoothAdapter﹕ getBluetoothService() called with no BluetoothManagerCallback   11-06 12:10:05.834    2136-2178/com.pietromaggi.sample.printing D/BluetoothSocket﹕ connect(), SocketState: INIT, mPfd: {ParcelFileDescriptor: FileDescriptor[59]}  

But I don't think that this is what generate the issue:

core/java/android/bluetooth/BluetoothAdapter.java - platform/frameworks/base - Git at Google

 

Any idea/suggestion?

 

~Pietro

 

public void pairPrinter()  {           final UUID SerialPortServiceClass_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");           final BluetoothAdapter BA = BluetoothAdapter.getDefaultAdapter();           final String PrinterBsid = "00:22:58:0E:E7:87"; // This is My Printer Bluetooth MAC Address                 Thread t = new Thread(new Runnable() {               @Override               public void run() {                   OutputStream sOut;                   BluetoothSocket socket;                   BA.cancelDiscovery();                         BluetoothDevice BD = BA.getRemoteDevice(PrinterBsid);                   try {                       socket = BD.createInsecureRfcommSocketToServiceRecord(SerialPortServiceClass_UUID);                   } catch (IOException e) {                       return;                   }                         if (!socket.isConnected()) {                       try {                           socket.connect();                           sOut = socket.getOutputStream();   //                        sOut.write(("Hello World\n").getBytes());                           String cpclData = "! 0 200 200 210 1\r\n"                                   + "TEXT 4 0 30 40 This is a CPCL test.\r\n"                                   + "FORM\r\n"                                   + "PRINT\r\n";                           sOut.write(cpclData.getBytes());                           sOut.close();                       } catch (IOException e) {                           e.printStackTrace();                       }                   }                         try {                       Thread.sleep(1000);                       socket.close();                       BA.cancelDiscovery();                   } catch (IOException e) {                       e.printStackTrace();                   } catch (InterruptedException e) {                       e.printStackTrace();                   }               }           });                 t.start();       }  

Anonymous (not verified)
Hi Pietro,You have the right

Hi Pietro,

You have the right idea here.  The issue is the way Android handles pairing with devices using Bluetooth standards 2.0 and below.  The only way to send info to these devices is to enter a pin on the device, or not pair at all and open a straight insecure socket to it.

Your code looks fine, but apparently KK made a small change to the socket creation method. Try this for lines 15-20 of your sample:

BluetoothDevice BD = BA.getRemoteDevice(PrinterBsid);

try {

    socket = BD.createRfcommSocketToServiceRecord();

} catch (Exception e) {Log.e("","Error creating socket");}

try {

    socket.connect();

    Log.e("","Connected");

} catch (IOException e) {

    Log.e("",e.getMessage());

    try {

          Log.e("","trying fallback...");

          socket =(BluetoothSocket) BD.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(BD,1);

    }

    catch (Exceptio e2) {

          Log.e("",e2.getMessage());
    }

}

You might still get the error, but you can ignore it and create the socket now.

Hope this works for you.

Vote: 
Vote up!
Vote down!

Points: 0

You voted ‘up’


Pietro Francesc...
Hi Robin,thanks for the help

Hi Robin,

thanks for the help.

The problem is that using your solution I get a request for a PIN code. And that is something that I'd like to avoid :-)

I think that the problem is that, with the changes in Android v4.4 I cannot start to write immediately on the Bluetooth Socket after having opened it. Putting my thread to sleep for 1s after opening the socket seems to work.

Now, I need to find a better (more reliable) way to wait for the socket to be usable.

Below code works on a TC70 with Android v4.4.3:

    public void pairPrinter()  {

        final UUID SerialPortServiceClass_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");

        final BluetoothAdapter BA = BluetoothAdapter.getDefaultAdapter();

        final String PrinterBsid = "00:22:58:0E:E7:87";

        Thread t = new Thread(new Runnable() {

            @Override

            public void run() {

                OutputStream sOut;

                BluetoothSocket socket;

                BA.cancelDiscovery();

                BluetoothDevice BD = BA.getRemoteDevice(PrinterBsid);

                try {

                    socket = BD.createInsecureRfcommSocketToServiceRecord(SerialPortServiceClass_UUID);


                    if (!socket.isConnected()) {

                        socket.connect();

                        Thread.sleep(1000); // <-- WAIT FOR SOCKET

                    }

                    sOut = socket.getOutputStream();

                    String cpclData = "! 0 200 200 210 1\r\n"

                            + "TEXT 4 0 30 40 This is a CPCL test.\r\n"

                            + "FORM\r\n"

                            + "PRINT\r\n";

                    sOut.write(cpclData.getBytes());

                    sOut.close();

                    socket.close();

                    BA.cancelDiscovery();

                } catch (IOException e) {

                    Log.e("","IOException");

                    e.printStackTrace();

                    return;

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        });

        t.start();

    }

~Pietro

Vote: 
Vote up!
Vote down!

Points: 1

You voted ‘up’


Pietro Francesc...
Just a note to explain that,

Just a note to explain that, while BluetoothSocket.connect was blocking on Android v4.1.2, this is no more the case for Android v4.4.3.


This is why it's possible with Android v4.1 to start using the socket as soon as it is open.

~Pietro

Vote: 
Vote up!
Vote down!

Points: 0

You voted ‘up’


Anonymous (not verified)
Just so I know how to answer,

Just so I know how to answer, I want to clarify.  Ignore pairing from the Android settings and just use the app.  Does the Java code you have in your post make the app ask for a pin?  It shouldn't, but I want to make sure. 

If this is the case, will it work for you?

For your timing issue, I know it's kind of kludgy, but have you tried:

if (!socket.isConnected()) { 

    socket.connect(); 

    int i = 3000;

    while ( ( !socket.isConnected() ) || ( i > 0 ) )

          i--;

}


It can take up to 3 seconds to establish a Bluetooth connection sometimes.  Not often, but just to be safe.


- Robin

Vote: 
Vote up!
Vote down!

Points: 0

You voted ‘up’


Pietro Francesc...
Hi Robin,the code I posted

Hi Robin,

the code I posted result in the printer printing without any pairing and without the need of an authentication PIN.

Regarding the timing issue, your code, or something more battery friendly like the one below does not works as it seems that isConnected returns true even if in reality is not...

                    if (!socket.isConnected()) {

                        socket.connect();

                    }

                    int limit = 1000; //

                    while ((limit>0) && (!socket.isConnected())) {

                        Thread.sleep(1);

                        limit--;

                    }

                    Log.d(TAG_BLUETOOTH, "limit is <" + limit + ">");

                    if (0 == limit) {

                        Log.e("","Slow socket!");

                        throw new IOException("SlowSocket");

                    }

In this case you always get limit is <1000>.

Debugging the code is of no help as you introduce more than enough delay if you insert a breakpoint after the socket.connect() call.

I'm looking for a way to register a callback without much luck at this moment.

~Pietro

Vote: 
Vote up!
Vote down!

Points: 0

You voted ‘up’


Anonymous (not verified)
Yeah, I haven't found

Yeah, I haven't found anything either. We'll keep looking though.  I'm trying to see if there's another socket or connection type to use and still get the same Bluetooth functionality.

What I find interesting is the Android APIs for BluetoothSocket still say:

public void connect ()

Attempt to connect to a remote device.

This method will block until a connection is made or the connection fails. If this method returns without an exception then this socket is now connected.

Vote: 
Vote up!
Vote down!

Points: 0

You voted ‘up’


Pietro Francesc...
The comment in this case is a

The comment in this case is a good example of why comments may be dangerous!

The implementation changed more than two years ago but the comment stayed the same.

Sometime writing clear code requiring a minimum of comments (or no comment at all) is much better :-)

BTW I've played a bit with broadcast receiver and I've seen that I can get the BluetoothDevice.ACTION_ACL_CONNECTED action.

I just need to see how to integrate this in my sample app :-)

~Pietro

Vote: 
Vote up!
Vote down!

Points: 0

You voted ‘up’


Andrea Hernandez
Hello guys!I have almost the

Hello guys!

I have almost the same issue. I hace a TC55 with Android 4.1.2 printing on iMZ320 and ZQ320. The problem is when I try print with the same routine from Samsung J2 Prime with Android 6.0.1. The routine just make Printer lights up blue led signaling a Bluetooth request but no printing (same case of before). I would love know how can I print, its very urgent!

Note. I am trying to print a lots of strings, because there are invoices, inventory products, etc.

Have a nice day!

Vote: 
Vote up!
Vote down!

Points: 0

You voted ‘up’


Anonymous (not verified)
Hi Andea, are you just

Hi Andea, are you just sending text to the printer?  The printers may not be in line mode to handle that.  Please send the following before you send the text you want to print:

! U1 setvar "device.languages" "line_print"

Followed by a carriage return.  See if this prints the way you want it to.

Robin

Vote: 
Vote up!
Vote down!

Points: 0

You voted ‘up’


Log in to post comments