Click here to Skip to main content
Click here to Skip to main content
Technical Blog

Tagged as

android: ipPrint4 print label/receipts to ip printer

, 11 May 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
ipPrint4 An android label/receipt printing app for TCP/IP connected printers This app is based on my btPrint4 app. In contrast to btPrint4 this time we print on TCP/IP connected printers. As with btPrint4 we have a main activity and one to list available printers and one to list available demo files

ipPrint4

An android label/receipt printing app for TCP/IP connected printers

This app is based on my btPrint4 app. In contrast to btPrint4 this time we print on TCP/IP connected printers.

As with btPrint4 we have a main activity and one to list available printers and one to list available demo files.

The challenge with ipPrint4 was a replacement for the bluetooth device discovery. This time we have to scan TCP/IP address range for port 9100. This port is also called HP direct printing port and supported by many printers. It behaves similar to telnet and you can just send print commands to the interface.

The second main change to btPrint4 was the printing code. This time we do not have to use a bluetooth socket but a network TCP/IP socket.

A TCP/IP portscanner

If you scan a range of IP addresses in sequence and try to open a port with a timeout of, let’s say 200ms, the scan will take (200msx254, scan from 1 to 254) 50 seconds.

Port scan code

    ...
    ScanResult portIsOpen1(String sIp, int p, int timeout){
        ScanResult scanResult=new ScanResult(sIp, p, false);
        try {
            Socket socket = new Socket();
            socket.connect(new InetSocketAddress(sIp, p), timeout);
            socket.close();
            scanResult = new ScanResult(sIp, p, true);// true;
        } catch (Exception ex) {
            doLog("Exception in portIsOpen1 for " + sIp + "/" + p);
            //return new ScanResult(ip, port, false);// false;
        }
        return scanResult;
    }
    ...
    //scanning 250 addresses with 200ms each will take 50 seconds if done one by one!
    for (int ip1=1; ip1<=254; ip1++){
        String sip=String.format(baseIP + ".%03d", ip1);
        scanResult = portIsOpen1(sip, port, timeout);
        if(scanResult.isOpen){
        Log.i(TAG, scanResult.sIP + " 9100 open");
        // Send the name of the connected device back to the UI Activity
        msg = mHandler.obtainMessage(msgTypes.addHost);
        bundle = new Bundle();
        bundle.putString(msgTypes.HOST_NAME, scanResult.sIP);
        msg.setData(bundle);
        mHandler.sendMessage(msg);
        doLog("added host msg for " + scanResult.sIP);
    }
    else{
        Log.i(TAG, scanResult.sIP + " 9100 unavailable");
    }
    if(backgroundThread.interrupted())
        break;
    }
    ...

That long time seems too much for me to let the user wait. Fortunately there is a post at stackoverflow showing how to use an executer service to run multiple port scans in parallel.

Start multiple port scans at once

    ...
    static Future<ScanResult> portIsOpen(final ExecutorService es, final String ip, final int port, final int timeout) {
        return es.submit(new Callable<ScanResult>() {
            @Override public ScanResult call() {
                try {
                    Socket socket = new Socket();
                    socket.connect(new InetSocketAddress(ip, port), timeout);
                    socket.close();
                    return new ScanResult(ip, port, true);// true;
                } catch (Exception ex) {
                    return new ScanResult(ip, port, false);// false;
                }
            }
        });
    }
    ...
    synchronized void startDiscovery1(){
        ...
        doLog("startDiscovery1: starting futures...");
        for (int ip1=1; ip1<=254; ip1++){
            String sip=String.format(baseIP + ".%03d", ip1);
            futures.add(portIsOpen(es, sip, port, timeout));
        }
        doLog("startDiscovery1: es.shutdown()");
        es.shutdown();
        int openPorts = 0;
        doLog("startDiscovery1 getting results...");
        for (final Future<ScanResult> f : futures) {
            try {
                if (f.get().isOpen) {
                    openPorts++;
                    Log.i("portScan:", f.get().sIP + " 9100 open");
                    // Send the name of the connected device back to the UI Activity
                    msg = mHandler.obtainMessage(msgTypes.addHost);
                    bundle = new Bundle();
                    bundle.putString(msgTypes.HOST_NAME, f.get().sIP);
                    msg.setData(bundle);
                    mHandler.sendMessage(msg);
                    doLog("added host msg for " + f.get().sIP);
                }
                else{
                    Log.i("portScan:", f.get().sIP + " 9100 closed");
                }
            }
            catch(ExecutionException e){
                doLog("ExecutionException: "+e.getMessage());
            }
            catch(InterruptedException e){
                doLog("InterruptedException: "+e.getMessage());
            }
        }
    ...
    }
    ...

Unfortunately the start of the Future objects is blocking and so I had to wrap that in another thread, so the caller (the GUI) is not blocked:

Thread to start port scans

    ...
    @Override
    public void run() {
        doLog("thread: run()...");
        if(state!=eState.idle) {
            doLog("thread: run() ended as state!=idle");
            return;
        }
        state=eState.running;
        ScanResult scanResult;
        try {
            doLog("thread: starting...");
            msg=mHandler.obtainMessage(msgTypes.started);
            mHandler.sendMessage(msg);
            if( true) { 
                doLog("thread: starting discovery...");
                startDiscovery1();
            ...
       ...
    ...

With the above solution the port scan finishes with 5 to 10 seconds. Every time a new open port is found the IP is added to the list. The process shows a progress cycle in the title of the list view activity. All status changes are send via a message handler from the portscanner code to the calling list activity.

When a TCP/IP address with port 9100 is found and selected you can print a demo file to it. You can also enter an IP manually and then directly print a demo file to it.

Startup checks

On startup the app checks for TCP/IP connectivity and ends, if there is no newtwork connection. It makes no sense to run the app without a working connection.

check online status

    ...
    public static boolean isNetworkOnline(Context context) {
        boolean status=false;
        try{
            ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo netInfo = cm.getNetworkInfo(0);
            if (netInfo != null && netInfo.getState()==NetworkInfo.State.CONNECTED) {
                status= true;
            }else {
                netInfo = cm.getNetworkInfo(1);
                if(netInfo!=null && netInfo.getState()==NetworkInfo.State.CONNECTED)
                    status= true;
            }
        }catch(Exception e){
            e.printStackTrace();
            return false;
        }
        return status;
    }
    ...

The local IP is automatically inserted as ‘printer’ IP address (remote device). So you just have to change part of the IP for the printer’s address.

IP address range

The portscanner code is initialized with the device IP and then uses a class C conversion to define start and end address. This will not work if you have a class B or class A network. So there are possible improvements of the code.

class c network

    ...
    //constructor
    PortScanner(Handler handler, String startIP){
        mHandler=handler;
        m_sStartIP=startIP;
        //test(startIP);
        String[] ss = m_sStartIP.split("\\.");
        if(ss.length!=4){ //no regular IP
            state=eState.finished;
            msg = mHandler.obtainMessage(hgo.ipprint4.msgTypes.MESSAGE_TOAST);
            bundle = new Bundle();
            bundle.putString(hgo.ipprint4.msgTypes.TOAST, "inavlid IP");
            msg.setData(bundle);
            mHandler.sendMessage(msg);
            return;
        }
        bValidIP=true;
        baseIP=ss[0]+"."+ss[1]+"."+ss[2];
        state=eState.idle;
    }
    ...

As we want to print to a socket, we needed to change the bluetooth connection code to connet to a network socket.

    ...
    /**
    * This thread runs while attempting to make an outgoing connection
    * with a device. It runs straight through; the connection either
    * succeeds or fails.
    */
    private class ConnectThread extends Thread {
        private Socket mmSocket=null;
        private InetAddress serverIP;
        private SocketAddress socketAddress=null;

        //private final BluetoothDevice mmDevice;

        @TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
        public ConnectThread(String sIPremote) {
            _IPaddr=sIPremote;
            try {
                addText("get host IP");
                serverIP=InetAddress.getByName(_IPaddr);
                socketAddress=new InetSocketAddress(serverIP, socketPort);
                //tmp = device.createRfcommSocketToServiceRecord(UUID_SPP);
            }catch (UnknownHostException e){
                Log.e(TAG, "ConnectThread create() failed", e);
            }
            catch (IOException e) {
                Log.e(TAG, "ConnectThread create() failed", e);
            }
        }
        @Override
        public void run() {
            Log.i(TAG, "ConnectThread::run()");
            setName("ConnectThread");
            Socket tmp = null;

            // Make a connection to the Socket
            try {

                addText("new Socket()...");
                // This is a blocking call and will only return on a
                // successful connection or an exception
                //tmp=new Socket(serverIP, socketPort);
                tmp=new Socket();
                tmp.connect(socketAddress, iTimeOut);
                addText("new socket() done");
                mmSocket=tmp;
            }
            catch(IllegalArgumentException e){
                addText("IllegalArgumentException: " + e.getMessage());
                //if new Socket() failed
                connectionFailed();
                addText("Connect failed");
                if(mmSocket!=null) {
                    // Close the socket
                    try {
                        mmSocket.close();
                        tmp = null;
                    } catch (IOException e2) {
                        Log.e(TAG, "unable to close() socket during connection failure", e2);
                    }
                }
                return;
            }
            catch (IOException e){
                addText("IOException: " + e.getMessage());
                //if new Socket() failed
                connectionFailed();
                addText("Connect failed");
                if(mmSocket!=null) {
                    // Close the socket
                    try {
                        mmSocket.close();
                        tmp = null;
                    } catch (IOException e2) {
                        Log.e(TAG, "unable to close() socket during connection failure", e2);
                    }
                }
                // Start the service over to restart listening mode
                return;
            }
            catch (Exception e) {
                //if new Socket() failed
                connectionFailed();
                addText("Connect failed");
                if(mmSocket!=null) {
                    // Close the socket
                    try {
                        mmSocket.close();
                        tmp = null;
                    } catch (IOException e2) {
                        Log.e(TAG, "unable to close() socket during connection failure", e2);
                    }
                }
                return;
            }//catch
    ...

Then we need a stream to write and one to listen. Same as in btPrint4

    ...
    private class ConnectedThread extends Thread {
        private final Socket mmSocket;
        private final InputStream mmInStream;
        private final OutputStream mmOutStream;

        public ConnectedThread(Socket socket) {
            Log.d(TAG, "create ConnectedThread");
            mmSocket = socket;
            InputStream tmpIn = null;
            OutputStream tmpOut = null;

            // Get the BluetoothSocket input and output streams
            try {
                tmpIn = socket.getInputStream();
                tmpOut = socket.getOutputStream();
            } catch (IOException e) {
                Log.e(TAG, "temp sockets not created", e);
            }

            mmInStream = tmpIn;
            mmOutStream = tmpOut;
        }
    ...

And then we can write to the socket.

    ...
        public void write(byte[] buffer) {
            addText("write...");
            try {
                mmOutStream.write(buffer);

                // Share the sent message back to the UI Activity
                mHandler.obtainMessage(msgTypes.MESSAGE_WRITE, -1, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                Log.e(TAG, "Exception during write", e);
            }
            addText("write done");
        }
    ...

The remaining code of ipPrintFile is similar to the one in btPrint4 and used to manage all the threads used.

Full Source Code at github

 

<!-- Social Bookmarks BEGIN --> <!-- Social Bookmarks END -->

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

hjgode

Germany Germany
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 PinprofessionalAhmed Bensaid19-Jun-14 3:47 
GeneralMy vote of 4 PinprofessionalSunasara Imdadhusen28-May-14 3:17 
QuestionTesting for a network connection PinmemberDarren_vms14-May-14 12:06 
AnswerRe: Testing for a network connection Pinmemberhjgode16-May-14 0:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141022.2 | Last Updated 11 May 2014
Article Copyright 2014 by hjgode
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid